extract a couple of files

[?]
Jun 3, 2022, 9:14 PM
LXTTOB33N2HCUZFIUDRQGGBVHK2HODRG4NBLH6RXRQZDCHF27BSAC

Dependencies

  • [2] EWQ2VRMS clean up test progress indicators
  • [3] IRJKWZWN .
  • [4] ORQKYYD7 .
  • [5] LS55YKGW switch copy/paste to ctrl- hotkeys
  • [6] 5FW7YOFT highlight selection while dragging
  • [7] NP7PIUBT bugfix: restore state after C-f (find)
  • [8] 6E3HVYWF test and App helper for mouse clicks
  • [9] XOAHJ6M3 similar tests for cursor up
  • [10] 2ENZW7TV select text using mouse drag
  • [11] YPHKZVWM extract a new variable
  • [12] H2DPLWMV snapshot: wrapping long lines at word boundaries
  • [13] BULPIBEG beginnings of a module for the text editor
  • [14] N6V6UJ3P this implementation undo load-tests quite poorly
  • [15] NQWWTGXR switch undo/redo to ctrl- hotkeys
  • [16] PFT5Y2ZY move
  • [17] U7M4M2F7 bugfix: don't rely on Screen_bottom1 while scrolling
  • [18] VJ77YABH more efficient undo/redo
  • [19] 5DOC2CBM extract a function
  • [20] XNFTJHC4 split keyboard handling between Text and Drawing
  • [21] HOSPP2AN crisp font rendering
  • [22] DHI6IJCN selecting text and deleting selections
  • [23] LERERVPH keep one screen line of overlap on pagedown
  • [24] S5VCAFKY couple of tests for cursor down
  • [25] R53OF3ON one bug I've repeatedly run into while testing with Moby Dick
  • [26] A2NV3WVO scrolling with up arrow
  • [27] 73OCE2MC after much struggle, a brute-force undo
  • [28] IMEJA43L snapshot
  • [29] YTSPVDZH first successful pagedown test, first bug found by test
  • [30] DFSDPDO7 bugfix
  • [31] V5MJRFOZ bugfix: down arrow doesn't scroll up unnecessarily
  • [32] WY3JD6W6 bugfix
  • [33] 4RUI5X52 a few tests for pageup, and a bugfix
  • [34] PHFWIFYK scroll on enter
  • [35] R5OKMVVC fix a regression in line wrapping
  • [36] BYG5CEMV support for naming points
  • [37] YYUGIYFV .
  • [38] QVDQMJXV avoid scrolling down if possible
  • [39] JF5L2BBS test harness now supports copy/paste
  • [40] KWOJ6XHE cut/copy selected text to clipboard
  • [41] Z4XRNDTR find text
  • [42] AD34IX2Z couple more tests
  • [43] QYIFOHW3 first test!
  • [44] HMODUNJE scroll on backspace
  • [45] TKFSYQ2Z up arrow to search previous
  • [*] R5QXEHUI somebody stop me

Change contents

  • file addition: text_tests.lua (----------)
    [47.2]
    -- major tests for text editing flows
    -- This still isn't quite as thorough as I'd like.
    function test_draw_text()
    io.write('\ntest_draw_text')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_draw_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_draw_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')
    end
    function test_draw_wrapping_text()
    io.write('\ntest_draw_wrapping_text')
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'defgh', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_draw_wrapping_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'gh', 'F - test_draw_wrapping_text/screen:3')
    end
    function test_draw_word_wrapping_text()
    io.write('\ntest_draw_word_wrapping_text')
    App.screen.init{width=60, height=60}
    Lines = load_array{'abc def ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    io.write('\ntest_draw_text_wrapping_within_word')
    App.screen.init{width=60, height=60}
    Lines = load_array{'abcd e fghijk', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')
    y = y + Line_height
    App.screen.check(y, 'e fghi', 'F - test_draw_text_wrapping_within_word/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jk', 'F - test_draw_text_wrapping_within_word/screen:3')
    end
    function test_edit_wrapping_text()
    io.write('\ntest_edit_wrapping_text')
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'def', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=4}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.run_after_textinput('g')
    App.run_after_textinput('h')
    App.run_after_textinput('i')
    App.run_after_textinput('j')
    App.run_after_textinput('k')
    App.run_after_textinput('l')
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_edit_wrapping_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghij', 'F - test_edit_wrapping_text/screen:3')
    end
    function test_insert_newline()
    io.write('\ntest_insert_newline')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    App.run_after_keychord('return')
    check_eq(Screen_top1.line, 1, 'F - test_insert_newline/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_insert_newline/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'a', 'F - test_insert_newline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/screen:3')
    end
    function test_insert_from_clipboard()
    io.write('\ntest_insert_from_clipboard')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    App.clipboard = 'xy\nz'
    App.run_after_keychord('C-v')
    check_eq(Screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')
    check_eq(Cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')
    y = y + Line_height
    App.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')
    end
    function test_move_cursor_using_mouse()
    io.write('\ntest_move_cursor_using_mouse')
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'def', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw() -- populate line.y for each line in Lines
    local screen_left_margin = 25 -- pixels
    App.run_after_mouserelease(screen_left_margin+8,Margin_top+5, '1')
    check_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
    check_eq(Cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')
    end
    function test_pagedown()
    io.write('\ntest_pagedown')
    App.screen.init{width=120, height=45}
    Lines = load_array{'abc', 'def', 'ghi'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- initially the first two lines are displayed
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pagedown/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pagedown/baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 2, 'F - test_pagedown/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_pagedown/cursor')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_pagedown/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
    end
    function test_pagedown_skips_drawings()
    io.write('\ntest_pagedown_skips_drawings')
    -- some lines of text with a drawing intermixed
    App.screen.init{width=50, height=80}
    Lines = load_array{'abc', -- height 15
    '```lines', '```', -- height 25
    'def', -- height 15
    'ghi'} -- height 15
    check_eq(Lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    local drawing_height = 20 + App.screen.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
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')
    -- after pagedown the screen draws the drawing up top
    -- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')
    y = Margin_top + drawing_height
    App.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')
    end
    function test_pagedown_shows_one_screen_line_in_common()
    io.write('\ntest_pagedown_shows_one_screen_line_in_common')
    -- some lines of text with a drawing intermixed
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'def ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:3')
    -- after pagedown the bottom screen line becomes the top
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:line')
    check_eq(Screen_top1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:pos')
    check_eq(Cursor1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_pagedown_shows_one_screen_line_in_common/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mn', 'F - test_pagedown_shows_one_screen_line_in_common/screen:3')
    end
    function test_down_arrow_moves_cursor()
    io.write('\ntest_down_arrow_moves_cursor')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- initially the first three lines are displayed
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 1, 'F - test_down_arrow_moves_cursor/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_down_arrow_moves_cursor/cursor')
    -- the screen is unchanged
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    io.write('\ntest_down_arrow_scrolls_down_by_one_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_line/screen_top')
    check_eq(Cursor1.line, 4, 'F - test_down_arrow_scrolls_down_by_one_line/cursor')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_line/screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:line')
    check_eq(Cursor1.pos, 6, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:2')
    y = y + Line_height
    App.screen.check(y, 'l', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:3')
    end
    function test_page_down_followed_by_down_arrow_does_not_scroll_screen_up()
    io.write('\ntest_page_down_followed_by_down_arrow_does_not_scroll_screen_up')
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
    -- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
    check_eq(Cursor1.pos, 6, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
    y = y + Line_height
    App.screen.check(y, 'l', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
    end
    function test_up_arrow_moves_cursor()
    io.write('\ntest_up_arrow_moves_cursor')
    -- display the first 3 lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    App.run_after_keychord('up')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_moves_cursor/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_up_arrow_moves_cursor/cursor')
    -- the screen is unchanged
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    io.write('\ntest_up_arrow_scrolls_up_by_one_line')
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    App.run_after_keychord('up')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/cursor')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_by_one_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    io.write('\ntest_up_arrow_scrolls_up_by_one_screen_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=6}
    Screen_top1 = {line=3, pos=5}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    App.run_after_keychord('up')
    y = Margin_top
    App.screen.check(y, 'ghi ', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3')
    check_eq(Screen_top1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')
    check_eq(Screen_top1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    io.write('\ntest_up_arrow_scrolls_up_to_final_screen_line')
    -- display lines starting just after a long line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    App.run_after_keychord('up')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')
    check_eq(Screen_top1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    io.write('\ntest_up_arrow_scrolls_up_to_empty_line')
    -- display a screenful of text with an empty line just above it outside the screen
    App.screen.init{width=120, height=60}
    Lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    App.run_after_keychord('up')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/cursor')
    y = Margin_top
    -- empty first line
    y = y + Line_height
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:3')
    end
    function test_pageup()
    io.write('\ntest_pageup')
    App.screen.init{width=120, height=45}
    Lines = load_array{'abc', 'def', 'ghi'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    -- initially the last two lines are displayed
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'def', 'F - test_pageup/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_pageup/baseline/screen:2')
    -- after pageup the cursor goes to first line
    App.run_after_keychord('pageup')
    check_eq(Screen_top1.line, 1, 'F - test_pageup/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_pageup/cursor')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pageup/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pageup/screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    io.write('\ntest_pageup_scrolls_up_by_screen_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    App.run_after_keychord('pageup')
    check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_by_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pageup_scrolls_up_by_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/screen:3')
    end
    function test_pageup_scrolls_up_from_middle_screen_line()
    io.write('\ntest_pageup_scrolls_up_from_middle_screen_line')
    -- display a few lines starting from the middle of a line (Cursor1.pos > 1)
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=5}
    Screen_top1 = {line=2, pos=5}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    App.run_after_keychord('pageup')
    check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    io.write('\ntest_enter_on_bottom_line_scrolls_down')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=2}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    App.run_after_keychord('return')
    check_eq(Screen_top1.line, 2, 'F - test_enter_on_bottom_line_scrolls_down/screen_top')
    check_eq(Cursor1.line, 4, 'F - test_enter_on_bottom_line_scrolls_down/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_enter_on_bottom_line_scrolls_down/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/screen:1')
    y = y + Line_height
    App.screen.check(y, 'g', 'F - test_enter_on_bottom_line_scrolls_down/screen:2')
    y = y + Line_height
    App.screen.check(y, 'hi', 'F - test_enter_on_bottom_line_scrolls_down/screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    io.write('\ntest_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom')
    -- display just the bottom line on screen
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=4, pos=2}
    Screen_top1 = {line=4, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    App.run_after_keychord('return')
    check_eq(Screen_top1.line, 4, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')
    check_eq(Cursor1.line, 5, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'j', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')
    y = y + Line_height
    App.screen.check(y, 'kl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    io.write('\ntest_position_cursor_on_recently_edited_wrapping_line')
    App.screen.init{width=120, height=200}
    Lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}
    Line_width = 100
    Cursor1 = {line=1, pos=25}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    -- I don't understand why 120px fits so much on a fake screen, but whatever..
    App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    App.run_after_textinput('s')
    App.run_after_textinput('t')
    App.run_after_textinput('u')
    check_eq(Cursor1.pos, 28, 'F - test_move_cursor_using_mouse/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2')
    y = y + Line_height
    App.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    local screen_left_margin = 25 -- pixels
    App.run_after_mouserelease(screen_left_margin+8,Margin_top+Line_height*2+5, '1')
    -- cursor should move
    check_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
    check_eq(Cursor1.pos, 26, 'F - test_move_cursor_using_mouse/cursor:pos')
    end
    function test_backspace_can_scroll_up()
    io.write('\ntest_backspace_can_scroll_up')
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'def', 'F - test_backspace_can_scroll_up/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    App.run_after_keychord('backspace')
    check_eq(Screen_top1.line, 1, 'F - test_backspace_can_scroll_up/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_backspace_can_scroll_up/cursor')
    y = Margin_top
    App.screen.check(y, 'abcdef', 'F - test_backspace_can_scroll_up/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    io.write('\ntest_backspace_can_scroll_up_screen_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=5}
    Screen_top1 = {line=3, pos=5}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    App.run_after_keychord('backspace')
    y = Margin_top
    App.screen.check(y, 'ghijk', 'F - test_backspace_can_scroll_up_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'l', 'F - test_backspace_can_scroll_up_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/screen:3')
    check_eq(Screen_top1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/screen_top')
    check_eq(Screen_top1.pos, 1, 'F - test_backspace_can_scroll_up_screen_line/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/cursor:line')
    check_eq(Cursor1.pos, 4, 'F - test_backspace_can_scroll_up_screen_line/cursor:pos')
    end
    -- some tests for operating over selections created using Shift- chords
    -- we're just testing delete_selection, and it works the same for all keys
    function test_backspace_over_selection()
    io.write('\ntest_backspace_over_selection')
    -- select just one character within a line with cursor before selection
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Selection1 = {line=1, pos=2}
    -- backspace deletes the selected character, even though it's after the cursor
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection/data")
    -- cursor (remains) at start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")
    check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_over_selection/selection")
    end
    function test_backspace_over_selection_reverse()
    io.write('\ntest_backspace_over_selection_reverse')
    -- select just one character within a line with cursor after selection
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Selection1 = {line=1, pos=1}
    -- backspace deletes the selected character
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")
    -- cursor moves to start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")
    check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_over_selection_reverse/selection")
    end
    function test_backspace_over_multiple_lines()
    io.write('\ntest_backspace_over_multiple_lines')
    -- select just one character within a line with cursor after selection
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Selection1 = {line=4, pos=2}
    -- backspace deletes the region and joins the remaining portions of lines on either side
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
    check_eq(Lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
    -- cursor remains at start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")
    check_eq(Cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_over_multiple_lines/selection")
    end
    function test_backspace_to_end_of_line()
    io.write('\ntest_backspace_to_end_of_line')
    -- select region from cursor to end of line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Selection1 = {line=1, pos=4}
    -- backspace deletes rest of line without joining to any other line
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_backspace_to_start_of_line()
    io.write('\ntest_backspace_to_start_of_line')
    -- select region from cursor to start of line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Selection1 = {line=2, pos=3}
    -- backspace deletes beginning of line without joining to any other line
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_undo_insert_text()
    io.write('\ntest_undo_insert_text')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=4}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- insert a character
    App.run_after_textinput('g')
    check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_insert_text/baseline/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_insert_text/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'defg', 'F - test_undo_insert_text/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_insert_text/baseline/screen:3')
    -- undo
    App.run_after_keychord('C-z')
    check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/cursor:line')
    check_eq(Cursor1.pos, 4, 'F - test_undo_insert_text/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_insert_text/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_insert_text/selection:pos')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_insert_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_undo_insert_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_insert_text/screen:3')
    end
    function test_undo_delete_text()
    io.write('\ntest_undo_delete_text')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'defg', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=5}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- delete a character
    App.run_after_keychord('backspace')
    check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/baseline/cursor:line')
    check_eq(Cursor1.pos, 4, 'F - test_undo_delete_text/baseline/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_delete_text/baseline/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_delete_text/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_undo_delete_text/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_delete_text/baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    App.run_after_keychord('C-z')
    check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_undo_delete_text/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_delete_text/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_delete_text/selection:pos')
    --? check_eq(Selection1.line, 2, 'F - test_undo_delete_text/selection:line')
    --? check_eq(Selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_delete_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'defg', 'F - test_undo_delete_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_delete_text/screen:3')
    end
  • replacement in text.lua at line 1
    [8.1][8.2:33]()
    -- primitives for editing text
    [8.1]
    [8.33]
    -- text editor, particularly text drawing, horizontal wrap, vertical scrolling
  • edit in text.lua at line 6
    [8.2775]
    [8.2775]
    require 'search'
    require 'select'
  • edit in text.lua at line 9
    [8.2790]
    [8.1567]
    require 'text_tests'
  • edit in text.lua at line 109
    [8.2728][8.764:797](),[8.797][8.472:498](),[8.498][8.831:1313](),[8.831][8.831:1313](),[8.1313][8.499:539](),[8.539][8.1362:1497](),[8.1362][8.1362:1497](),[8.1497][8.540:607](),[8.607][8.1581:2104](),[8.1581][8.1581:2104](),[8.2104][8.2:130](),[8.130][7.2:194](),[7.194][8.220:709](),[8.220][8.220:709](),[8.709][8.2104:2232](),[8.2104][8.2104:2232](),[8.2232][8.710:941](),[8.941][7.195:387](),[7.387][8.2322:2539](),[8.2322][8.2322:2539](),[8.2539][8.1067:1068](),[8.2728][8.1067:1068](),[8.1068][8.942:1240](),[8.1240][6.2:270](),[6.270][8.1224:1556](),[8.1224][8.1224:1556](),[8.1556][6.271:409](),[6.409][8.1601:2613](),[8.1601][8.1601:2613](),[8.2613][6.410:826](),[6.826][8.2613:2619](),[8.2613][8.2613:2619](),[8.2619][6.827:872](),[6.872][8.2619:2624](),[8.2619][8.2619:2624](),[8.2624][6.873:1098](),[6.1098][8.2624:2657](),[8.2624][8.2624:2657](),[8.2657][8.920:1173](),[8.1173][8.2657:3719](),[8.2657][8.2657:3719](),[8.3719][8.100:101](),[8.2796][8.100:101](),[8.101][8.1:850]()
    function Text.draw_search_bar()
    local h = Line_height+2
    local y = App.screen.height-h
    love.graphics.setColor(0.9,0.9,0.9)
    love.graphics.rectangle('fill', 0, y-10, App.screen.width-1, h+8)
    love.graphics.setColor(0.6,0.6,0.6)
    love.graphics.line(0, y-10, App.screen.width-1, y-10)
    love.graphics.setColor(1,1,1)
    love.graphics.rectangle('fill', 20, y-6, App.screen.width-40, h+2, 2,2)
    love.graphics.setColor(0.6,0.6,0.6)
    love.graphics.rectangle('line', 20, y-6, App.screen.width-40, h+2, 2,2)
    love.graphics.setColor(0,0,0)
    App.screen.print(Search_term, 25,y-5)
    love.graphics.setColor(1,0,0)
    if Search_text == nil then
    Search_text = App.newText(love.graphics.getFont(), Search_term)
    end
    love.graphics.circle('fill', 25+App.width(Search_text),y-5+h, 2)
    love.graphics.setColor(0,0,0)
    end
    function Text.search_next()
    -- search current line
    local pos = Lines[Cursor1.line].data:find(Search_term, Cursor1.pos)
    if pos then
    Cursor1.pos = pos
    end
    if pos == nil then
    for i=Cursor1.line+1,#Lines do
    pos = Lines[i].data:find(Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    -- wrap around
    for i=1,Cursor1.line-1 do
    pos = Lines[i].data:find(Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    Cursor1.line = Search_backup.cursor.line
    Cursor1.pos = Search_backup.cursor.pos
    Screen_top1.line = Search_backup.screen_top.line
    Screen_top1.pos = Search_backup.screen_top.pos
    end
    if Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) then
    Screen_top1.line = Cursor1.line
    local _, pos = Text.pos_at_start_of_cursor_screen_line()
    Screen_top1.pos = pos
    end
    end
    function Text.search_previous()
    -- search current line
    local pos = rfind(Lines[Cursor1.line].data, Search_term, Cursor1.pos)
    if pos then
    Cursor1.pos = pos
    end
    if pos == nil then
    for i=Cursor1.line-1,1,-1 do
    pos = rfind(Lines[i].data, Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    -- wrap around
    for i=#Lines,Cursor1.line+1,-1 do
    pos = rfind(Lines[i].data, Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    Cursor1.line = Search_backup.cursor.line
    Cursor1.pos = Search_backup.cursor.pos
    Screen_top1.line = Search_backup.screen_top.line
    Screen_top1.pos = Search_backup.screen_top.pos
    end
    if Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) then
    Screen_top1.line = Cursor1.line
    local _, pos = Text.pos_at_start_of_cursor_screen_line()
    Screen_top1.pos = pos
    end
    end
    function rfind(s, pat, i)
    local rs = s:reverse()
    local rpat = pat:reverse()
    if i == nil then i = #s end
    local ri = #s - i + 1
    local rendpos = rs:find(rpat, ri)
    if rendpos == nil then return nil end
    local endpos = #s - rendpos + 1
    assert (endpos >= #pat)
    return endpos-#pat+1
    end
    -- Return any intersection of the region from Selection1 to Cursor1 (or
    -- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and
    -- currently over a drawing) with the region between {line=line_index, pos=apos}
    -- and {line=line_index, pos=bpos}.
    -- apos must be less than bpos. However Selection1 and Cursor1 can be in any order.
    -- Result: positions spos,epos between apos,bpos.
    function Text.clip_selection(line_index, apos, bpos)
    if Selection1.line == nil then return nil,nil end
    -- min,max = sorted(Selection1,Cursor1)
    local minl,minp = Selection1.line,Selection1.pos
    local maxl,maxp
    if love.mouse.isDown('1') then
    maxl,maxp = Text.mouse_pos()
    else
    maxl,maxp = Cursor1.line,Cursor1.pos
    end
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    -- check if intervals are disjoint
    if line_index < minl then return nil,nil end
    if line_index > maxl then return nil,nil end
    if line_index == minl and bpos <= minp then return nil,nil end
    if line_index == maxl and apos >= maxp then return nil,nil end
    -- compare bounds more carefully (start inclusive, end exclusive)
    local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})
    local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})
    --? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)
    if a_ge and b_lt then
    -- fully contained
    return apos,bpos
    elseif a_ge then
    assert(maxl == line_index)
    return apos,maxp
    elseif b_lt then
    assert(minl == line_index)
    return minp,bpos
    else
    assert(minl == maxl and minl == line_index)
    return minp,maxp
    end
    end
    -- inefficient for some reason, so don't do it on every frame
    function Text.mouse_pos()
    local time = love.timer.getTime()
    if Recent_mouse.time and Recent_mouse.time > time-0.1 then
    return Recent_mouse.line, Recent_mouse.pos
    end
    Recent_mouse.time = time
    local line,pos = Text.to_pos(love.mouse.getX(), love.mouse.getY())
    if line then
    Recent_mouse.line = line
    Recent_mouse.pos = pos
    end
    return Recent_mouse.line, Recent_mouse.pos
    end
    function Text.to_pos(x,y)
    for line_index,line in ipairs(Lines) do
    if line.mode == 'text' then
    if Text.in_line(line, x,y) then
    return line_index, Text.to_pos_on_line(line, x,y)
    end
    end
    end
    end
    function Text.delete_selection()
    local minl,maxl = minmax(Selection1.line, Cursor1.line)
    local before = snapshot(minl, maxl)
    Text.delete_selection_without_undo()
    record_undo_event({before=before, after=snapshot(Cursor1.line)})
    end
    function Text.delete_selection_without_undo()
    if Selection1.line == nil then return end
    -- min,max = sorted(Selection1,Cursor1)
    local minl,minp = Selection1.line,Selection1.pos
    local maxl,maxp = Cursor1.line,Cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    -- update Cursor1 and Selection1
    Cursor1.line = minl
    Cursor1.pos = minp
    Selection1 = {}
    -- delete everything between min (inclusive) and max (exclusive)
    Lines[minl].fragments = nil
    Lines[minl].screen_line_starting_pos = nil
    local min_offset = utf8.offset(Lines[minl].data, minp)
    local max_offset = utf8.offset(Lines[maxl].data, maxp)
    if minl == maxl then
    --? print('minl == maxl')
    Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..Lines[minl].data:sub(max_offset)
    return
    end
    assert(minl < maxl)
    local rhs = Lines[maxl].data:sub(max_offset)
    for i=maxl,minl+1,-1 do
    table.remove(Lines, i)
    end
    Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..rhs
    end
    function Text.selection()
    if Selection1.line == nil then return end
    -- min,max = sorted(Selection1,Cursor1)
    local minl,minp = Selection1.line,Selection1.pos
    local maxl,maxp = Cursor1.line,Cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    local min_offset = utf8.offset(Lines[minl].data, minp)
    local max_offset = utf8.offset(Lines[maxl].data, maxp)
    if minl == maxl then
    return Lines[minl].data:sub(min_offset, max_offset-1)
    end
    assert(minl < maxl)
    local result = Lines[minl].data:sub(min_offset)..'\n'
    for i=minl+1,maxl-1 do
    if Lines[i].mode == 'text' then
    result = result..Lines[i].data..'\n'
    end
    end
    result = result..Lines[maxl].data:sub(1, max_offset-1)
    return result
    end
  • edit in text.lua at line 110
    [8.851][8.851:962]()
    function Text.cut_selection()
    local result = Text.selection()
    Text.delete_selection()
    return result
    end
  • edit in text.lua at line 118
    [8.42][8.111:137](),[8.137][2.1:32](),[2.32][8.137:219](),[8.197][8.137:219](),[8.137][8.137:219](),[8.219][8.265:297](),[8.297][8.238:320](),[8.238][8.238:320](),[8.331][8.331:344](),[8.344][8.2:25](),[8.25][8.452:512](),[8.452][8.452:512](),[8.512][8.688:710](),[8.710][8.534:594](),[8.534][8.534:594](),[8.594][8.711:733](),[8.733][8.616:681](),[8.616][8.616:681](),[8.681][8.1:273](),[8.284][8.284:297](),[8.297][8.26:49](),[8.49][8.405:474](),[8.405][8.405:474](),[8.474][8.734:756](),[8.756][8.496:565](),[8.496][8.496:565](),[8.565][8.757:779](),[8.779][8.587:660](),[8.587][8.587:660](),[8.660][8.1:282](),[8.293][8.293:306](),[8.306][8.50:73](),[8.73][8.414:489](),[8.414][8.414:489](),[8.489][8.780:802](),[8.802][8.511:586](),[8.511][8.511:586](),[8.586][8.803:825](),[8.825][8.608:1048](),[8.608][8.608:1048](),[8.1059][8.1059:1072](),[8.1072][8.74:97](),[8.97][8.1180:1263](),[8.1180][8.1180:1263](),[8.1263][8.826:848](),[8.848][8.1285:1369](),[8.1285][8.1285:1369](),[8.1369][8.849:871](),[8.871][8.1391:1476](),[8.1391][8.1391:1476](),[8.1476][8.660:930](),[8.660][8.660:930](),[8.941][8.941:1127](),[8.1127][8.98:121](),[8.121][8.1235:1304](),[8.1235][8.1235:1304](),[8.1304][8.872:894](),[8.894][8.1326:1395](),[8.1326][8.1326:1395](),[8.1395][8.895:917](),[8.917][8.1417:1487](),[8.1417][8.1417:1487](),[8.1487][8.2:331](),[8.437][8.437:450](),[8.450][8.122:145](),[8.145][8.480:554](),[8.480][8.480:554](),[8.554][8.918:940](),[8.940][8.576:650](),[8.576][8.576:650](),[8.650][8.941:963](),[8.963][8.672:1040](),[8.672][8.672:1040](),[8.1040][8.146:163](),[8.163][8.1064:1127](),[8.1064][8.1064:1127](),[8.1127][8.964:986](),[8.986][8.1149:1213](),[8.1149][8.1149:1213](),[8.1213][8.987:1009](),[8.1009][8.1235:1300](),[8.1235][8.1235:1300](),[8.1300][8.1487:1492](),[8.1487][8.1487:1492](),[8.1492][8.1301:1639](),[8.1745][8.1745:1758](),[8.1758][8.164:187](),[8.187][8.1788:1869](),[8.1788][8.1788:1869](),[8.1869][8.1010:1032](),[8.1032][8.1891:1972](),[8.1891][8.1891:1972](),[8.1972][8.1033:1055](),[8.1055][8.1994:2158](),[8.1994][8.1994:2158](),[8.2158][5.2:34](),[5.34][8.2190:2413](),[8.2190][8.2190:2413](),[8.2413][8.188:205](),[8.205][8.2437:2509](),[8.2437][8.2437:2509](),[8.2509][8.1056:1078](),[8.1078][8.2531:2603](),[8.2531][8.2531:2603](),[8.2603][8.1079:1101](),[8.1101][8.2625:2702](),[8.2625][8.2625:2702](),[8.2702][8.1:281](),[8.1492][8.1:281](),[8.292][8.292:348](),[8.390][8.390:433](),[8.433][8.2:71](),[8.71][8.507:662](),[8.273][8.507:662](),[8.507][8.507:662](),[8.662][8.198:223](),[8.1492][8.198:223](),[8.681][8.198:223](),[8.223][2.33:63](),[2.63][8.248:330](),[8.248][8.248:330](),[8.330][8.298:330](),[8.330][8.349:431](),[8.349][8.349:431](),[8.537][8.537:599](),[8.599][8.274:297](),[8.297][8.629:697](),[8.629][8.629:697](),[8.697][8.1102:1124](),[8.1124][8.719:876](),[8.719][8.719:876](),[8.876][8.331:451](),[8.451][8.298:315](),[8.315][8.900:959](),[8.900][8.900:959](),[8.959][8.1125:1147](),[8.1147][8.981:1045](),[8.981][8.981:1045](),[8.1045][8.1:41](),[8.41][2.64:109](),[8.81][8.1123:1173](),[2.109][8.1123:1173](),[8.1123][8.1123:1173](),[8.1173][8.23:282](),[8.282][8.82:170](),[8.170][8.1423:1537](),[8.1423][8.1423:1537](),[8.308][8.308:538](),[8.538][8.1722:1735](),[8.1722][8.1722:1735](),[8.1735][8.316:339](),[8.339][8.171:254](),[8.1765][8.171:254](),[8.254][8.452:508](),[8.508][8.539:640](),[8.1902][8.539:640](),[8.640][8.1902:1939](),[8.1902][8.1902:1939](),[8.1939][8.509:659](),[8.659][8.340:374](),[8.374][8.300:379](),[8.1980][8.300:379](),[8.379][8.706:1078](),[8.1184][8.1184:1197](),[8.1197][8.375:398](),[8.398][8.1227:1327](),[8.1227][8.1227:1327](),[8.1327][8.1148:1170](),[8.1170][8.1349:1450](),[8.1349][8.1349:1450](),[8.1450][8.1171:1193](),[8.1193][8.1472:2053](),[8.1472][8.1472:2053](),[8.2053][8.399:416](),[8.416][8.2077:2169](),[8.2077][8.2077:2169](),[8.2169][8.1194:1216](),[8.1216][8.2191:2282](),[8.2191][8.2191:2282](),[8.2282][8.1217:1239](),[8.1239][8.2304:2399](),[8.2304][8.2304:2399](),[8.2399][8.379:419](),[8.379][8.379:419](),[8.419][2.110:155](),[2.155][8.459:548](),[8.459][8.459:548](),[8.548][8.660:692](),[8.692][8.567:649](),[8.567][8.567:649](),[8.755][8.755:819](),[8.819][8.417:440](),[8.440][8.849:932](),[8.849][8.849:932](),[8.932][8.1240:1262](),[8.1262][8.954:1037](),[8.954][8.954:1037](),[8.1037][8.1263:1285](),[8.1285][8.1059:1142](),[8.1059][8.1059:1142](),[8.1142][8.693:760](),[8.760][8.1200:1233](),[8.1200][8.1200:1233](),[8.1233][8.1:80](),[8.80][8.838:938](),[8.838][8.838:938](),[8.938][8.441:458](),[8.458][8.1257:1331](),[8.1257][8.1257:1331](),[8.1331][8.1286:1308](),[8.1308][8.1353:1427](),[8.1353][8.1353:1427](),[8.1427][8.1309:1331](),[8.1331][8.1449:1523](),[8.1449][8.1449:1523](),[8.1635][8.1635:1692](),[8.1692][2.156:213](),[2.213][8.1744:1903](),[8.1744][8.1744:1903](),[8.1903][8.939:971](),[8.971][8.1922:2004](),[8.1922][8.1922:2004](),[8.2110][8.2110:2123](),[8.2123][8.459:482](),[8.482][8.2153:2248](),[8.2153][8.2153:2248](),[8.2248][8.1332:1354](),[8.1354][8.2270:2365](),[8.2270][8.2270:2365](),[8.2365][8.1355:1377](),[8.1377][8.2387:2585](),[8.2387][8.2387:2585](),[8.2585][8.972:1146](),[8.1146][8.483:500](),[8.500][8.2609:2695](),[8.2609][8.2609:2695](),[8.2695][8.1378:1400](),[8.1400][8.2717:2803](),[8.2717][8.2717:2803](),[8.2803][8.1401:1423](),[8.1423][8.2825:2911](),[8.2825][8.2825:2911](),[8.2911][8.1147:1211](),[8.1211][2.214:278](),[2.278][8.1270:1549](),[8.1270][8.1270:1549](),[8.1655][8.1655:1668](),[8.1668][8.501:524](),[8.524][8.1698:1800](),[8.1698][8.1698:1800](),[8.1800][8.1424:1446](),[8.1446][8.1822:1924](),[8.1822][8.1822:1924](),[8.1924][8.1447:1469](),[8.1469][8.1946:2485](),[8.1946][8.1946:2485](),[8.2485][8.525:542](),[8.542][8.2509:2602](),[8.2509][8.2509:2602](),[8.2602][8.1470:1492](),[8.1492][8.2624:2718](),[8.2624][8.2624:2718](),[8.2718][8.1493:1515](),[8.1515][8.2750:2935](),[8.2750][8.2750:2935](),[8.2935][2.279:371](),[2.371][8.3022:3300](),[8.3022][8.3022:3300](),[8.3406][8.3406:3419](),[8.3419][8.543:566](),[8.566][8.3449:3579](),[8.3449][8.3449:3579](),[8.3579][8.1516:1538](),[8.1538][8.3601:3731](),[8.3601][8.3601:3731](),[8.3731][8.1539:1561](),[8.1561][8.3753:4358](),[8.3753][8.3753:4358](),[8.4358][8.567:584](),[8.584][8.4382:4503](),[8.4382][8.4382:4503](),[8.4503][8.1562:1584](),[8.1584][8.4525:4648](),[8.4525][8.4525:4648](),[8.4648][8.1585:1607](),[8.1607][8.4670:4789](),[8.4670][8.4670:4789](),[8.4789][8.1:369](),[8.475][8.475:488](),[8.488][8.585:608](),[8.608][8.518:636](),[8.518][8.518:636](),[8.636][8.1608:1630](),[8.1630][8.658:776](),[8.658][8.658:776](),[8.776][8.1631:1653](),[8.1653][8.798:1864](),[8.798][8.798:1864](),[8.1864][8.609:626](),[8.626][8.1888:1999](),[8.1888][8.1888:1999](),[8.1999][8.1654:1676](),[8.1676][8.2021:2128](),[8.2021][8.2021:2128](),[8.2128][8.1677:1699](),[8.1699][8.2150:2259](),[8.2150][8.2150:2259](),[8.2259][8.279:322](),[8.4789][8.279:322](),[8.279][8.279:322](),[8.322][2.372:415](),[2.415][8.360:616](),[8.360][8.360:616](),[8.722][8.722:735](),[8.735][8.627:650](),[8.650][8.765:846](),[8.765][8.765:846](),[8.846][8.1700:1722](),[8.1722][8.868:949](),[8.868][8.868:949](),[8.949][8.1723:1745](),[8.1745][8.971:1052](),[8.971][8.971:1052](),[8.1052][8.4790:4852](),[8.4852][8.1108:1139](),[8.1108][8.1108:1139](),[8.1139][8.4853:5028](),[8.5028][8.651:668](),[8.668][8.1163:1235](),[8.1163][8.1163:1235](),[8.1235][8.1746:1768](),[8.1768][8.1257:1329](),[8.1257][8.1257:1329](),[8.1329][8.1769:1791](),[8.1791][8.1351:1423](),[8.1351][8.1351:1423](),[8.1608][8.1608:1661](),[8.1661][2.416:469](),[2.469][8.1709:1954](),[8.1709][8.1709:1954](),[8.2060][8.2060:2073](),[8.2073][8.669:692](),[8.692][8.2103:2194](),[8.2103][8.2103:2194](),[8.2194][8.1792:1814](),[8.1814][8.2216:2307](),[8.2216][8.2216:2307](),[8.2307][8.1815:1837](),[8.1837][8.2329:2517](),[8.2329][8.2329:2517](),[8.2517][8.5029:5195](),[8.5195][8.693:710](),[8.710][8.2541:2623](),[8.2541][8.2541:2623](),[8.2623][8.1838:1860](),[8.1860][8.2645:2727](),[8.2645][8.2645:2727](),[8.2727][8.1861:1883](),[8.1883][8.2749:2831](),[8.2749][8.2749:2831](),[8.2831][8.5196:5256](),[8.5256][2.470:530](),[2.530][8.1:63](),[8.5311][8.1:63](),[8.63][8.552:647](),[8.552][8.552:647](),[8.647][8.5455:5487](),[8.5455][8.5455:5487](),[8.5487][8.648:708](),[8.708][8.5547:5569](),[8.5547][8.5547:5569](),[8.5675][8.5675:5688](),[8.5688][8.711:734](),[8.734][8.709:807](),[8.5718][8.709:807](),[8.807][8.1884:1906](),[8.1906][8.808:906](),[8.5958][8.808:906](),[8.906][8.64:139](),[8.139][8.6122:6153](),[8.6122][8.6122:6153](),[8.6153][8.735:752](),[8.752][8.907:997](),[8.6177][8.907:997](),[8.997][8.1907:1929](),[8.1929][8.998:1087](),[8.6288][8.998:1087](),[8.1087][8.1930:1952](),[8.1952][8.1088:1364](),[8.6399][8.1088:1364](),[8.1364][3.1:181](),[3.181][8.2148:2153](),[8.1535][8.2148:2153](),[8.2911][8.2148:2153](),[8.2997][8.2148:2153](),[8.6668][8.2148:2153](),[8.2148][8.2148:2153](),[8.2153][8.140:197](),[8.197][2.531:593](),[2.593][8.254:514](),[8.254][8.254:514](),[8.620][8.620:633](),[8.633][8.753:776](),[8.776][8.663:763](),[8.663][8.663:763](),[8.763][8.1953:1975](),[8.1975][8.785:885](),[8.785][8.785:885](),[8.885][8.1976:1998](),[8.1998][8.907:1130](),[8.907][8.907:1130](),[8.1130][8.777:794](),[8.794][8.1154:1245](),[8.1154][8.1154:1245](),[8.1245][8.1999:2021](),[8.2021][8.1267:1358](),[8.1267][8.1267:1358](),[8.1358][8.2022:2044](),[8.2044][8.1380:1662](),[8.1380][8.1380:1662](),[8.1662][3.182:366](),[3.366][8.1837:1842](),[8.1837][8.1837:1842](),[8.1842][8.81:131](),[8.131][8.1:56](),[8.56][8.188:467](),[8.188][8.188:467](),[8.573][8.573:586](),[8.586][8.795:818](),[8.818][8.57:150](),[8.616][8.57:150](),[8.150][8.2045:2067](),[8.2067][8.151:244](),[8.733][8.151:244](),[8.244][8.2068:2090](),[8.2090][8.245:338](),[8.850][8.245:338](),[8.338][8.945:1042](),[8.945][8.945:1042](),[8.1042][8.339:509](),[8.509][8.819:836](),[8.836][8.1240:1262](),[8.1240][8.1240:1262](),[8.1262][8.2091:2113](),[8.2113][8.510:594](),[8.1284][8.510:594](),[8.594][8.2114:2136](),[8.2136][8.595:679](),[8.1392][8.595:679](),[8.679][8.1478:1483](),[8.1478][8.1478:1483](),[8.1483][8.1:248](),[8.1842][8.1:248](),[8.354][8.354:415](),[8.415][8.837:860](),[8.860][8.445:511](),[8.445][8.445:511](),[8.511][8.2137:2159](),[8.2159][8.533:798](),[8.533][8.533:798](),[8.798][8.861:878](),[8.878][8.822:879](),[8.822][8.822:879](),[8.879][8.2160:2182](),[8.2182][8.901:1345](),[8.901][8.901:1345](),[8.1451][8.1451:1464](),[8.1464][8.879:902](),[8.902][8.1494:1586](),[8.1494][8.1494:1586](),[8.1586][8.2183:2205](),[8.2205][8.1608:1700](),[8.1608][8.1608:1700](),[8.1700][8.2206:2228](),[8.2228][8.1722:2216](),[8.1722][8.1722:2216](),[8.2216][8.903:920](),[8.920][8.2240:2324](),[8.2240][8.2240:2324](),[8.2324][8.2229:2251](),[8.2251][8.2346:2429](),[8.2346][8.2346:2429](),[8.2429][8.2252:2274](),[8.2274][8.2451:2944](),[8.2451][8.2451:2944](),[8.3050][8.3050:3063](),[8.3063][8.921:944](),[8.944][8.3093:3194](),[8.3093][8.3093:3194](),[8.3194][8.2275:2297](),[8.2297][8.3216:3746](),[8.3216][8.3216:3746](),[8.3746][8.945:962](),[8.962][8.3770:3863](),[8.3770][8.3770:3863](),[8.3863][8.2298:2320](),[8.2320][8.3885:3977](),[8.3885][8.3885:3977](),[8.3977][8.2321:2343](),[8.2343][8.3999:4092](),[8.3999][8.3999:4092](),[8.4092][8.1:368](),[8.474][8.474:487](),[8.487][8.963:986](),[8.986][8.517:610](),[8.517][8.517:610](),[8.610][8.2344:2366](),[8.2366][8.632:725](),[8.632][8.632:725](),[8.725][8.2367:2389](),[8.2389][8.747:1191](),[8.747][8.747:1191](),[8.1191][8.987:1004](),[8.1004][8.1215:1299](),[8.1215][8.1215:1299](),[8.1299][8.2390:2412](),[8.2412][8.1321:1403](),[8.1321][8.1321:1403](),[8.1403][8.2413:2435](),[8.2435][8.1425:1508](),[8.1425][8.1425:1508](),[8.1508][8.4092:4097](),[8.4092][8.4092:4097](),[8.4097][8.1:409](),[8.515][8.515:528](),[8.528][8.1005:1028](),[8.1028][8.558:1118](),[8.558][8.558:1118](),[8.1118][8.1029:1046](),[8.1046][8.1142:1251](),[8.1142][8.1142:1251](),[8.1251][8.2436:2458](),[8.2458][8.1273:1388](),[8.1273][8.1273:1388](),[8.1388][8.1:381](),[8.4097][8.1:381](),[8.392][8.392:405](),[8.405][8.1047:1070](),[8.1070][8.513:711](),[8.513][8.513:711](),[8.711][8.2459:2481](),[8.2481][8.733:851](),[8.733][8.733:851](),[8.851][8.2482:2504](),[8.2504][8.873:1211](),[8.873][8.873:1211](),[8.1211][8.1071:1088](),[8.1088][8.1235:1353](),[8.1235][8.1235:1353](),[8.1353][8.2505:2527](),[8.2527][8.1375:1493](),[8.1375][8.1375:1493](),[8.1493][8.2528:2550](),[8.2550][8.1515:1707](),[8.1515][8.1515:1707](),[8.1749][8.1749:1792](),[8.1792][8.72:155](),[8.155][8.1871:2046](),[8.1170][8.1871:2046](),[8.1871][8.1871:2046](),[8.2046][8.1:336](),[8.442][8.442:455](),[8.455][8.1171:1194](),[8.1194][8.485:568](),[8.485][8.485:568](),[8.568][8.2551:2573](),[8.2573][8.590:673](),[8.590][8.590:673](),[8.673][8.2574:2596](),[8.2596][8.695:1029](),[8.695][8.695:1029](),[8.1029][8.1195:1212](),[8.1212][8.1053:1130](),[8.1053][8.1053:1130](),[8.1130][8.2597:2619](),[8.2619][8.1152:1226](),[8.1152][8.1152:1226](),[8.1226][8.2620:2642](),[8.2642][8.1248:1707](),[8.1248][8.1248:1707](),[8.1813][8.1813:1826](),[8.1826][8.1213:1236](),[8.1236][8.1856:1951](),[8.1856][8.1856:1951](),[8.1951][8.2643:2665](),[8.2665][8.1973:2176](),[8.1973][8.1973:2176](),[8.2176][8.1237:1254](),[8.1254][8.2200:2288](),[8.2200][8.2200:2288](),[8.2288][8.2666:2688](),[8.2688][8.2310:2394](),[8.2310][8.2310:2394](),[8.2394][8.2689:2711](),[8.2711][8.2416:2857](),[8.2416][8.2416:2857](),[8.2857][8.3720:4223](),[8.4234][8.4234:4725](),[8.2857][8.2046:2051](),[8.4725][8.2046:2051](),[8.2046][8.2046:2051](),[8.2051][8.4726:4775](),[8.4775][4.1:55](),[4.55][8.4821:5083](),[8.4821][8.4821:5083](),[8.5094][8.5094:5629](),[8.5629][4.56:107](),[4.107][8.5675:5937](),[8.5675][8.5675:5937](),[8.5948][8.5948:6598](),[8.6598][4.108:154](),[4.154][8.6644:6879](),[8.6644][8.6644:6879](),[8.6890][8.6890:7504](),[8.7504][4.155:203](),[4.203][8.7550:7787](),[8.7550][8.7550:7787](),[8.7798][8.7798:8374](),[8.8374][8.2791:3058](),[8.3069][8.3069:3440](),[8.3440][8.1255:1278](),[8.1278][8.3548:3624](),[8.3548][8.3548:3624](),[8.3624][8.2712:2734](),[8.2734][8.3646:3723](),[8.3646][8.3646:3723](),[8.3723][8.2735:2757](),[8.2757][8.3745:3831](),[8.3745][8.3745:3831](),[8.3831][8.2:34](),[8.34][8.3863:4143](),[8.3863][8.3863:4143](),[8.4143][8.1279:1296](),[8.1296][8.4167:4234](),[8.4167][8.4167:4234](),[8.4234][8.2758:2780](),[8.2780][8.4256:4323](),[8.4256][8.4256:4323](),[8.4323][8.2781:2803](),[8.2803][8.4345:4685](),[8.4345][8.4345:4685](),[8.4696][8.4696:5074](),[8.5074][8.1297:1320](),[8.1320][8.5182:5258](),[8.5182][8.5182:5258](),[8.5258][8.2804:2826](),[8.2826][8.5280:5356](),[8.5280][8.5280:5356](),[8.5356][8.2827:2849](),[8.2849][8.5378:5516](),[8.5378][8.5378:5516](),[8.5516][8.35:67](),[8.67][8.5548:5984](),[8.5548][8.5548:5984](),[8.5984][8.1321:1338](),[8.1338][8.6008:6075](),[8.6008][8.6008:6075](),[8.6075][8.2850:2872](),[8.2872][8.6097:6165](),[8.6097][8.6097:6165](),[8.6165][8.2873:2895](),[8.2895][8.6187:6254](),[8.6187][8.6187:6254](),[8.614][8.6254:6259](),[8.6254][8.6254:6259]()
    function test_draw_text()
    io.write('\ntest_draw_text')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_draw_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_draw_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')
    end
    function test_draw_wrapping_text()
    io.write('\ntest_draw_wrapping_text')
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'defgh', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_draw_wrapping_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'gh', 'F - test_draw_wrapping_text/screen:3')
    end
    function test_draw_word_wrapping_text()
    io.write('\ntest_draw_word_wrapping_text')
    App.screen.init{width=60, height=60}
    Lines = load_array{'abc def ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')
    end
    function test_draw_text_wrapping_within_word()
    -- arrange a screen line that needs to be split within a word
    io.write('\ntest_draw_text_wrapping_within_word')
    App.screen.init{width=60, height=60}
    Lines = load_array{'abcd e fghijk', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')
    y = y + Line_height
    App.screen.check(y, 'e fghi', 'F - test_draw_text_wrapping_within_word/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jk', 'F - test_draw_text_wrapping_within_word/screen:3')
    end
    function test_edit_wrapping_text()
    io.write('\ntest_edit_wrapping_text')
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'def', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=4}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.run_after_textinput('g')
    App.run_after_textinput('h')
    App.run_after_textinput('i')
    App.run_after_textinput('j')
    App.run_after_textinput('k')
    App.run_after_textinput('l')
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_edit_wrapping_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghij', 'F - test_edit_wrapping_text/screen:3')
    end
    function test_insert_newline()
    io.write('\ntest_insert_newline')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    App.run_after_keychord('return')
    check_eq(Screen_top1.line, 1, 'F - test_insert_newline/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_insert_newline/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'a', 'F - test_insert_newline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_newline/screen:3')
    end
    function test_insert_from_clipboard()
    io.write('\ntest_insert_from_clipboard')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    App.clipboard = 'xy\nz'
    App.run_after_keychord('C-v')
    check_eq(Screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')
    check_eq(Cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')
    y = y + Line_height
    App.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')
    end
    function test_move_cursor_using_mouse()
    io.write('\ntest_move_cursor_using_mouse')
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'def', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw() -- populate line.y for each line in Lines
    local screen_left_margin = 25 -- pixels
    App.run_after_mouserelease(screen_left_margin+8,Margin_top+5, '1')
    check_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
    check_eq(Cursor1.pos, 2, 'F - test_move_cursor_using_mouse/cursor:pos')
    end
    function test_pagedown()
    io.write('\ntest_pagedown')
    App.screen.init{width=120, height=45}
    Lines = load_array{'abc', 'def', 'ghi'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- initially the first two lines are displayed
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pagedown/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pagedown/baseline/screen:2')
    -- after pagedown the bottom line becomes the top
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 2, 'F - test_pagedown/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_pagedown/cursor')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_pagedown/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_pagedown/screen:2')
    end
    function test_pagedown_skips_drawings()
    io.write('\ntest_pagedown_skips_drawings')
    -- some lines of text with a drawing intermixed
    App.screen.init{width=50, height=80}
    Lines = load_array{'abc', -- height 15
    '```lines', '```', -- height 25
    'def', -- height 15
    'ghi'} -- height 15
    check_eq(Lines[2].mode, 'drawing', 'F - test_pagedown_skips_drawings/baseline/lines')
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    local drawing_height = 20 + App.screen.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
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pagedown_skips_drawings/baseline/screen:1')
    -- after pagedown the screen draws the drawing up top
    -- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 2, 'F - test_pagedown_skips_drawings/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_pagedown_skips_drawings/cursor')
    y = Margin_top + drawing_height
    App.screen.check(y, 'def', 'F - test_pagedown_skips_drawings/screen:1')
    end
    function test_pagedown_shows_one_screen_line_in_common()
    io.write('\ntest_pagedown_shows_one_screen_line_in_common')
    -- some lines of text with a drawing intermixed
    App.screen.init{width=50, height=60}
    Lines = load_array{'abc', 'def ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:3')
    -- after pagedown the bottom screen line becomes the top
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:line')
    check_eq(Screen_top1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/screen_top:pos')
    check_eq(Cursor1.line, 2, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_pagedown_shows_one_screen_line_in_common/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'ghi ', 'F - test_pagedown_shows_one_screen_line_in_common/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_pagedown_shows_one_screen_line_in_common/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mn', 'F - test_pagedown_shows_one_screen_line_in_common/screen:3')
    end
    function test_down_arrow_moves_cursor()
    io.write('\ntest_down_arrow_moves_cursor')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- initially the first three lines are displayed
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/baseline/screen:3')
    -- after hitting the down arrow, the cursor moves down by 1 line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 1, 'F - test_down_arrow_moves_cursor/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_down_arrow_moves_cursor/cursor')
    -- the screen is unchanged
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_moves_cursor/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_moves_cursor/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_moves_cursor/screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_line()
    io.write('\ntest_down_arrow_scrolls_down_by_one_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_line/screen_top')
    check_eq(Cursor1.line, 4, 'F - test_down_arrow_scrolls_down_by_one_line/cursor')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_down_arrow_scrolls_down_by_one_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_line/screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line()
    io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the down arrow the screen scrolls down by one line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_down_arrow_scrolls_down_by_one_screen_line/screen:3')
    end
    function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
    io.write('\ntest_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:3')
    -- after hitting the down arrow the screen scrolls down by one line
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 2, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:line')
    check_eq(Cursor1.pos, 6, 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghijk', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:2')
    y = y + Line_height
    App.screen.check(y, 'l', 'F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:3')
    end
    function test_page_down_followed_by_down_arrow_does_not_scroll_screen_up()
    io.write('\ntest_page_down_followed_by_down_arrow_does_not_scroll_screen_up')
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')
    -- after hitting pagedown the screen scrolls down to start of a long line
    App.run_after_keychord('pagedown')
    check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')
    -- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
    App.run_after_keychord('down')
    check_eq(Screen_top1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')
    check_eq(Cursor1.pos, 6, 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'ghijk', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')
    y = y + Line_height
    App.screen.check(y, 'l', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')
    end
    function test_up_arrow_moves_cursor()
    io.write('\ntest_up_arrow_moves_cursor')
    -- display the first 3 lines with the cursor on the bottom line
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=3, pos=1}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/baseline/screen:3')
    -- after hitting the up arrow the cursor moves up by 1 line
    App.run_after_keychord('up')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_moves_cursor/screen_top')
    check_eq(Cursor1.line, 2, 'F - test_up_arrow_moves_cursor/cursor')
    -- the screen is unchanged
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_line()
    io.write('\ntest_up_arrow_scrolls_up_by_one_line')
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    App.run_after_keychord('up')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/cursor')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_by_one_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/screen:3')
    end
    function test_up_arrow_scrolls_up_by_one_screen_line()
    io.write('\ntest_up_arrow_scrolls_up_by_one_screen_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=6}
    Screen_top1 = {line=3, pos=5}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2')
    -- after hitting the up arrow the screen scrolls up to first screen line
    App.run_after_keychord('up')
    y = Margin_top
    App.screen.check(y, 'ghi ', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3')
    check_eq(Screen_top1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')
    check_eq(Screen_top1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_final_screen_line()
    io.write('\ntest_up_arrow_scrolls_up_to_final_screen_line')
    -- display lines starting just after a long line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up to final screen line of previous line
    App.run_after_keychord('up')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')
    check_eq(Screen_top1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos')
    end
    function test_up_arrow_scrolls_up_to_empty_line()
    io.write('\ntest_up_arrow_scrolls_up_to_empty_line')
    -- display a screenful of text with an empty line just above it outside the screen
    App.screen.init{width=120, height=60}
    Lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3')
    -- after hitting the up arrow the screen scrolls up by one line
    App.run_after_keychord('up')
    check_eq(Screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/cursor')
    y = Margin_top
    -- empty first line
    y = y + Line_height
    App.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:3')
    end
    function test_pageup()
    io.write('\ntest_pageup')
    App.screen.init{width=120, height=45}
    Lines = load_array{'abc', 'def', 'ghi'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    -- initially the last two lines are displayed
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'def', 'F - test_pageup/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_pageup/baseline/screen:2')
    -- after pageup the cursor goes to first line
    App.run_after_keychord('pageup')
    check_eq(Screen_top1.line, 1, 'F - test_pageup/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_pageup/cursor')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_pageup/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pageup/screen:2')
    end
    function test_pageup_scrolls_up_by_screen_line()
    io.write('\ntest_pageup_scrolls_up_by_screen_line')
    -- display the first three lines with the cursor on the bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    App.run_after_keychord('pageup')
    check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_by_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pageup_scrolls_up_by_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/screen:3')
    end
    function test_pageup_scrolls_up_from_middle_screen_line()
    io.write('\ntest_pageup_scrolls_up_from_middle_screen_line')
    -- display a few lines starting from the middle of a line (Cursor1.pos > 1)
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=5}
    Screen_top1 = {line=2, pos=5}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace
    -- after hitting the page-up key the screen scrolls up to top
    App.run_after_keychord('pageup')
    check_eq(Screen_top1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:3')
    end
    function test_enter_on_bottom_line_scrolls_down()
    io.write('\ntest_enter_on_bottom_line_scrolls_down')
    -- display a few lines with cursor on bottom line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=2}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3')
    -- after hitting the enter key the screen scrolls down
    App.run_after_keychord('return')
    check_eq(Screen_top1.line, 2, 'F - test_enter_on_bottom_line_scrolls_down/screen_top')
    check_eq(Cursor1.line, 4, 'F - test_enter_on_bottom_line_scrolls_down/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_enter_on_bottom_line_scrolls_down/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/screen:1')
    y = y + Line_height
    App.screen.check(y, 'g', 'F - test_enter_on_bottom_line_scrolls_down/screen:2')
    y = y + Line_height
    App.screen.check(y, 'hi', 'F - test_enter_on_bottom_line_scrolls_down/screen:3')
    end
    function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
    io.write('\ntest_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom')
    -- display just the bottom line on screen
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = App.screen.width
    Cursor1 = {line=4, pos=2}
    Screen_top1 = {line=4, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1')
    -- after hitting the enter key the screen does not scroll down
    App.run_after_keychord('return')
    check_eq(Screen_top1.line, 4, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')
    check_eq(Cursor1.line, 5, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')
    check_eq(Cursor1.pos, 1, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'j', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')
    y = y + Line_height
    App.screen.check(y, 'kl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2')
    end
    function test_position_cursor_on_recently_edited_wrapping_line()
    -- draw a line wrapping over 2 screen lines
    io.write('\ntest_position_cursor_on_recently_edited_wrapping_line')
    App.screen.init{width=120, height=200}
    Lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}
    Line_width = 100
    Cursor1 = {line=1, pos=25}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    -- I don't understand why 120px fits so much on a fake screen, but whatever..
    App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3')
    -- add to the line until it's wrapping over 3 screen lines
    App.run_after_textinput('s')
    App.run_after_textinput('t')
    App.run_after_textinput('u')
    check_eq(Cursor1.pos, 28, 'F - test_move_cursor_using_mouse/cursor:pos')
    y = Margin_top
    App.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1')
    y = y + Line_height
    App.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2')
    y = y + Line_height
    App.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')
    -- try to move the cursor earlier in the third screen line by clicking the mouse
    local screen_left_margin = 25 -- pixels
    App.run_after_mouserelease(screen_left_margin+8,Margin_top+Line_height*2+5, '1')
    -- cursor should move
    check_eq(Cursor1.line, 1, 'F - test_move_cursor_using_mouse/cursor:line')
    check_eq(Cursor1.pos, 26, 'F - test_move_cursor_using_mouse/cursor:pos')
    end
    function test_backspace_can_scroll_up()
    io.write('\ntest_backspace_can_scroll_up')
    -- display the lines 2/3/4 with the cursor on line 2
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl'}
    Line_width = 120
    Cursor1 = {line=2, pos=1}
    Screen_top1 = {line=2, pos=1}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'def', 'F - test_backspace_can_scroll_up/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/baseline/screen:3')
    -- after hitting backspace the screen scrolls up by one line
    App.run_after_keychord('backspace')
    check_eq(Screen_top1.line, 1, 'F - test_backspace_can_scroll_up/screen_top')
    check_eq(Cursor1.line, 1, 'F - test_backspace_can_scroll_up/cursor')
    y = Margin_top
    App.screen.check(y, 'abcdef', 'F - test_backspace_can_scroll_up/screen:1')
    y = y + Line_height
    App.screen.check(y, 'ghi', 'F - test_backspace_can_scroll_up/screen:2')
    y = y + Line_height
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up/screen:3')
    end
    function test_backspace_can_scroll_up_screen_line()
    io.write('\ntest_backspace_can_scroll_up_screen_line')
    -- display lines starting from second screen line of a line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=3, pos=5}
    Screen_top1 = {line=3, pos=5}
    Screen_bottom1 = {}
    App.draw()
    local y = Margin_top
    App.screen.check(y, 'jkl', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/baseline/screen:2')
    -- after hitting backspace the screen scrolls up by one screen line
    App.run_after_keychord('backspace')
    y = Margin_top
    App.screen.check(y, 'ghijk', 'F - test_backspace_can_scroll_up_screen_line/screen:1')
    y = y + Line_height
    App.screen.check(y, 'l', 'F - test_backspace_can_scroll_up_screen_line/screen:2')
    y = y + Line_height
    App.screen.check(y, 'mno', 'F - test_backspace_can_scroll_up_screen_line/screen:3')
    check_eq(Screen_top1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/screen_top')
    check_eq(Screen_top1.pos, 1, 'F - test_backspace_can_scroll_up_screen_line/screen_top')
    check_eq(Cursor1.line, 3, 'F - test_backspace_can_scroll_up_screen_line/cursor:line')
    check_eq(Cursor1.pos, 4, 'F - test_backspace_can_scroll_up_screen_line/cursor:pos')
    end
    -- some tests for operating over selections created using Shift- chords
    -- we're just testing delete_selection, and it works the same for all keys
    function test_backspace_over_selection()
    io.write('\ntest_backspace_over_selection')
    -- select just one character within a line with cursor before selection
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=1}
    Selection1 = {line=1, pos=2}
    -- backspace deletes the selected character, even though it's after the cursor
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection/data")
    -- cursor (remains) at start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_over_selection/cursor:line")
    check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_over_selection/selection")
    end
    function test_backspace_over_selection_reverse()
    io.write('\ntest_backspace_over_selection_reverse')
    -- select just one character within a line with cursor after selection
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Selection1 = {line=1, pos=1}
    -- backspace deletes the selected character
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'bc', "F - test_backspace_over_selection_reverse/data")
    -- cursor moves to start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_over_selection_reverse/cursor:line")
    check_eq(Cursor1.pos, 1, "F - test_backspace_over_selection_reverse/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_over_selection_reverse/selection")
    end
    function test_backspace_over_multiple_lines()
    io.write('\ntest_backspace_over_multiple_lines')
    -- select just one character within a line with cursor after selection
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Selection1 = {line=4, pos=2}
    -- backspace deletes the region and joins the remaining portions of lines on either side
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'akl', "F - test_backspace_over_multiple_lines/data:1")
    check_eq(Lines[2].data, 'mno', "F - test_backspace_over_multiple_lines/data:2")
    -- cursor remains at start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_over_multiple_lines/cursor:line")
    check_eq(Cursor1.pos, 2, "F - test_backspace_over_multiple_lines/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_over_multiple_lines/selection")
    end
    function test_backspace_to_end_of_line()
    io.write('\ntest_backspace_to_end_of_line')
    -- select region from cursor to end of line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=1, pos=2}
    Selection1 = {line=1, pos=4}
    -- backspace deletes rest of line without joining to any other line
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'a', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Lines[2].data, 'def', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Cursor1.line, 1, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Cursor1.pos, 2, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_backspace_to_start_of_line()
    io.write('\ntest_backspace_to_start_of_line')
    -- select region from cursor to start of line
    App.screen.init{width=25+30, height=60}
    Lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=1}
    Selection1 = {line=2, pos=3}
    -- backspace deletes beginning of line without joining to any other line
    App.run_after_keychord('backspace')
    check_eq(Lines[1].data, 'abc', "F - test_backspace_to_start_of_line/data:1")
    check_eq(Lines[2].data, 'f', "F - test_backspace_to_start_of_line/data:2")
    -- cursor remains at start of selection
    check_eq(Cursor1.line, 2, "F - test_backspace_to_start_of_line/cursor:line")
    check_eq(Cursor1.pos, 1, "F - test_backspace_to_start_of_line/cursor:pos")
    -- selection is cleared
    check_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")
    end
    function test_undo_insert_text()
    io.write('\ntest_undo_insert_text')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'def', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=4}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- insert a character
    App.run_after_textinput('g')
    check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_insert_text/baseline/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_insert_text/baseline/selection:pos')
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_insert_text/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'defg', 'F - test_undo_insert_text/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_insert_text/baseline/screen:3')
    -- undo
    App.run_after_keychord('C-z')
    check_eq(Cursor1.line, 2, 'F - test_undo_insert_text/cursor:line')
    check_eq(Cursor1.pos, 4, 'F - test_undo_insert_text/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_insert_text/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_insert_text/selection:pos')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_insert_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_undo_insert_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_insert_text/screen:3')
    end
    function test_undo_delete_text()
    io.write('\ntest_undo_delete_text')
    App.screen.init{width=120, height=60}
    Lines = load_array{'abc', 'defg', 'xyz'}
    Line_width = App.screen.width
    Cursor1 = {line=2, pos=5}
    Screen_top1 = {line=1, pos=1}
    Screen_bottom1 = {}
    -- delete a character
    App.run_after_keychord('backspace')
    check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/baseline/cursor:line')
    check_eq(Cursor1.pos, 4, 'F - test_undo_delete_text/baseline/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_delete_text/baseline/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_delete_text/baseline/selection:pos')
    local y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_delete_text/baseline/screen:1')
    y = y + Line_height
    App.screen.check(y, 'def', 'F - test_undo_delete_text/baseline/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_delete_text/baseline/screen:3')
    -- undo
    --? -- after undo, the backspaced key is selected
    App.run_after_keychord('C-z')
    check_eq(Cursor1.line, 2, 'F - test_undo_delete_text/cursor:line')
    check_eq(Cursor1.pos, 5, 'F - test_undo_delete_text/cursor:pos')
    check_nil(Selection1.line, 'F - test_undo_delete_text/selection:line')
    check_nil(Selection1.pos, 'F - test_undo_delete_text/selection:pos')
    --? check_eq(Selection1.line, 2, 'F - test_undo_delete_text/selection:line')
    --? check_eq(Selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')
    y = Margin_top
    App.screen.check(y, 'abc', 'F - test_undo_delete_text/screen:1')
    y = y + Line_height
    App.screen.check(y, 'defg', 'F - test_undo_delete_text/screen:2')
    y = y + Line_height
    App.screen.check(y, 'xyz', 'F - test_undo_delete_text/screen:3')
    end
  • file addition: select.lua (----------)
    [47.2]
    -- helpers for selecting portions of text
    local utf8 = require 'utf8'
    -- Return any intersection of the region from Selection1 to Cursor1 (or
    -- current mouse, if mouse is pressed; or recent mouse if mouse is pressed and
    -- currently over a drawing) with the region between {line=line_index, pos=apos}
    -- and {line=line_index, pos=bpos}.
    -- apos must be less than bpos. However Selection1 and Cursor1 can be in any order.
    -- Result: positions spos,epos between apos,bpos.
    function Text.clip_selection(line_index, apos, bpos)
    if Selection1.line == nil then return nil,nil end
    -- min,max = sorted(Selection1,Cursor1)
    local minl,minp = Selection1.line,Selection1.pos
    local maxl,maxp
    if love.mouse.isDown('1') then
    maxl,maxp = Text.mouse_pos()
    else
    maxl,maxp = Cursor1.line,Cursor1.pos
    end
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    -- check if intervals are disjoint
    if line_index < minl then return nil,nil end
    if line_index > maxl then return nil,nil end
    if line_index == minl and bpos <= minp then return nil,nil end
    if line_index == maxl and apos >= maxp then return nil,nil end
    -- compare bounds more carefully (start inclusive, end exclusive)
    local a_ge = Text.le1({line=minl, pos=minp}, {line=line_index, pos=apos})
    local b_lt = Text.lt1({line=line_index, pos=bpos}, {line=maxl, pos=maxp})
    --? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)
    if a_ge and b_lt then
    -- fully contained
    return apos,bpos
    elseif a_ge then
    assert(maxl == line_index)
    return apos,maxp
    elseif b_lt then
    assert(minl == line_index)
    return minp,bpos
    else
    assert(minl == maxl and minl == line_index)
    return minp,maxp
    end
    end
    -- inefficient for some reason, so don't do it on every frame
    function Text.mouse_pos()
    local time = love.timer.getTime()
    if Recent_mouse.time and Recent_mouse.time > time-0.1 then
    return Recent_mouse.line, Recent_mouse.pos
    end
    Recent_mouse.time = time
    local line,pos = Text.to_pos(love.mouse.getX(), love.mouse.getY())
    if line then
    Recent_mouse.line = line
    Recent_mouse.pos = pos
    end
    return Recent_mouse.line, Recent_mouse.pos
    end
    function Text.to_pos(x,y)
    for line_index,line in ipairs(Lines) do
    if line.mode == 'text' then
    if Text.in_line(line, x,y) then
    return line_index, Text.to_pos_on_line(line, x,y)
    end
    end
    end
    end
    function Text.delete_selection()
    local minl,maxl = minmax(Selection1.line, Cursor1.line)
    local before = snapshot(minl, maxl)
    Text.delete_selection_without_undo()
    record_undo_event({before=before, after=snapshot(Cursor1.line)})
    end
    function Text.delete_selection_without_undo()
    if Selection1.line == nil then return end
    -- min,max = sorted(Selection1,Cursor1)
    local minl,minp = Selection1.line,Selection1.pos
    local maxl,maxp = Cursor1.line,Cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    -- update Cursor1 and Selection1
    Cursor1.line = minl
    Cursor1.pos = minp
    Selection1 = {}
    -- delete everything between min (inclusive) and max (exclusive)
    Lines[minl].fragments = nil
    Lines[minl].screen_line_starting_pos = nil
    local min_offset = utf8.offset(Lines[minl].data, minp)
    local max_offset = utf8.offset(Lines[maxl].data, maxp)
    if minl == maxl then
    --? print('minl == maxl')
    Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..Lines[minl].data:sub(max_offset)
    return
    end
    assert(minl < maxl)
    local rhs = Lines[maxl].data:sub(max_offset)
    for i=maxl,minl+1,-1 do
    table.remove(Lines, i)
    end
    Lines[minl].data = Lines[minl].data:sub(1, min_offset-1)..rhs
    end
    function Text.selection()
    if Selection1.line == nil then return end
    -- min,max = sorted(Selection1,Cursor1)
    local minl,minp = Selection1.line,Selection1.pos
    local maxl,maxp = Cursor1.line,Cursor1.pos
    if minl > maxl then
    minl,maxl = maxl,minl
    minp,maxp = maxp,minp
    elseif minl == maxl then
    if minp > maxp then
    minp,maxp = maxp,minp
    end
    end
    local min_offset = utf8.offset(Lines[minl].data, minp)
    local max_offset = utf8.offset(Lines[maxl].data, maxp)
    if minl == maxl then
    return Lines[minl].data:sub(min_offset, max_offset-1)
    end
    assert(minl < maxl)
    local result = Lines[minl].data:sub(min_offset)..'\n'
    for i=minl+1,maxl-1 do
    if Lines[i].mode == 'text' then
    result = result..Lines[i].data..'\n'
    end
    end
    result = result..Lines[maxl].data:sub(1, max_offset-1)
    return result
    end
    function Text.cut_selection()
    local result = Text.selection()
    Text.delete_selection()
    return result
    end
  • file addition: search.lua (----------)
    [47.2]
    -- helpers for the search bar (C-f)
    function Text.draw_search_bar()
    local h = Line_height+2
    local y = App.screen.height-h
    love.graphics.setColor(0.9,0.9,0.9)
    love.graphics.rectangle('fill', 0, y-10, App.screen.width-1, h+8)
    love.graphics.setColor(0.6,0.6,0.6)
    love.graphics.line(0, y-10, App.screen.width-1, y-10)
    love.graphics.setColor(1,1,1)
    love.graphics.rectangle('fill', 20, y-6, App.screen.width-40, h+2, 2,2)
    love.graphics.setColor(0.6,0.6,0.6)
    love.graphics.rectangle('line', 20, y-6, App.screen.width-40, h+2, 2,2)
    love.graphics.setColor(0,0,0)
    App.screen.print(Search_term, 25,y-5)
    love.graphics.setColor(1,0,0)
    if Search_text == nil then
    Search_text = App.newText(love.graphics.getFont(), Search_term)
    end
    love.graphics.circle('fill', 25+App.width(Search_text),y-5+h, 2)
    love.graphics.setColor(0,0,0)
    end
    function Text.search_next()
    -- search current line
    local pos = Lines[Cursor1.line].data:find(Search_term, Cursor1.pos)
    if pos then
    Cursor1.pos = pos
    end
    if pos == nil then
    for i=Cursor1.line+1,#Lines do
    pos = Lines[i].data:find(Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    -- wrap around
    for i=1,Cursor1.line-1 do
    pos = Lines[i].data:find(Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    Cursor1.line = Search_backup.cursor.line
    Cursor1.pos = Search_backup.cursor.pos
    Screen_top1.line = Search_backup.screen_top.line
    Screen_top1.pos = Search_backup.screen_top.pos
    end
    if Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) then
    Screen_top1.line = Cursor1.line
    local _, pos = Text.pos_at_start_of_cursor_screen_line()
    Screen_top1.pos = pos
    end
    end
    function Text.search_previous()
    -- search current line
    local pos = rfind(Lines[Cursor1.line].data, Search_term, Cursor1.pos)
    if pos then
    Cursor1.pos = pos
    end
    if pos == nil then
    for i=Cursor1.line-1,1,-1 do
    pos = rfind(Lines[i].data, Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    -- wrap around
    for i=#Lines,Cursor1.line+1,-1 do
    pos = rfind(Lines[i].data, Search_term)
    if pos then
    Cursor1.line = i
    Cursor1.pos = pos
    break
    end
    end
    end
    if pos == nil then
    Cursor1.line = Search_backup.cursor.line
    Cursor1.pos = Search_backup.cursor.pos
    Screen_top1.line = Search_backup.screen_top.line
    Screen_top1.pos = Search_backup.screen_top.pos
    end
    if Text.lt1(Cursor1, Screen_top1) or Text.lt1(Screen_bottom1, Cursor1) then
    Screen_top1.line = Cursor1.line
    local _, pos = Text.pos_at_start_of_cursor_screen_line()
    Screen_top1.pos = pos
    end
    end
    function rfind(s, pat, i)
    local rs = s:reverse()
    local rpat = pat:reverse()
    if i == nil then i = #s end
    local ri = #s - i + 1
    local rendpos = rs:find(rpat, ri)
    if rendpos == nil then return nil end
    local endpos = #s - rendpos + 1
    assert (endpos >= #pat)
    return endpos-#pat+1
    end