Merge lines.love
[text.love.git] / text_tests.lua
blob7bf2221e2615e27ab2096032025ee6d0a9ae4329
1 -- major tests for text editing flows
2 -- Arguably this should be called edit_tests.lua,
3 -- but that would mess up the git blame at this point.
5 function test_initial_state()
6 App.screen.init{width=120, height=60}
7 Editor_state = edit.initialize_test_state()
8 Editor_state.lines = load_array{}
9 Text.redraw_all(Editor_state)
10 edit.draw(Editor_state)
11 check_eq(#Editor_state.lines, 1, '#lines')
12 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
13 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
14 check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
15 check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
16 end
18 function test_backspace_from_start_of_final_line()
19 -- display final line of text with cursor at start of it
20 App.screen.init{width=120, height=60}
21 Editor_state = edit.initialize_test_state()
22 Editor_state.lines = load_array{'abc', 'def'}
23 Editor_state.screen_top1 = {line=2, pos=1}
24 Editor_state.cursor1 = {line=2, pos=1}
25 Text.redraw_all(Editor_state)
26 -- backspace scrolls up
27 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
28 check_eq(#Editor_state.lines, 1, '#lines')
29 check_eq(Editor_state.cursor1.line, 1, 'cursor')
30 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
31 end
33 function test_insert_first_character()
34 App.screen.init{width=120, height=60}
35 Editor_state = edit.initialize_test_state()
36 Editor_state.lines = load_array{}
37 Text.redraw_all(Editor_state)
38 edit.draw(Editor_state)
39 edit.run_after_text_input(Editor_state, 'a')
40 local y = Editor_state.top
41 App.screen.check(y, 'a', 'screen:1')
42 end
44 function test_press_ctrl()
45 -- press ctrl while the cursor is on text
46 App.screen.init{width=50, height=80}
47 Editor_state = edit.initialize_test_state()
48 Editor_state.lines = load_array{''}
49 Text.redraw_all(Editor_state)
50 Editor_state.cursor1 = {line=1, pos=1}
51 Editor_state.screen_top1 = {line=1, pos=1}
52 edit.run_after_keychord(Editor_state, 'C-m', 'm')
53 end
55 function test_move_left()
56 App.screen.init{width=120, height=60}
57 Editor_state = edit.initialize_test_state()
58 Editor_state.lines = load_array{'a'}
59 Text.redraw_all(Editor_state)
60 Editor_state.cursor1 = {line=1, pos=2}
61 edit.draw(Editor_state)
62 edit.run_after_keychord(Editor_state, 'left', 'left')
63 check_eq(Editor_state.cursor1.pos, 1, 'check')
64 end
66 function test_move_right()
67 App.screen.init{width=120, height=60}
68 Editor_state = edit.initialize_test_state()
69 Editor_state.lines = load_array{'a'}
70 Text.redraw_all(Editor_state)
71 Editor_state.cursor1 = {line=1, pos=1}
72 edit.draw(Editor_state)
73 edit.run_after_keychord(Editor_state, 'right', 'right')
74 check_eq(Editor_state.cursor1.pos, 2, 'check')
75 end
77 function test_move_left_to_previous_line()
78 App.screen.init{width=120, height=60}
79 Editor_state = edit.initialize_test_state()
80 Editor_state.lines = load_array{'abc', 'def'}
81 Text.redraw_all(Editor_state)
82 Editor_state.cursor1 = {line=2, pos=1}
83 edit.draw(Editor_state)
84 edit.run_after_keychord(Editor_state, 'left', 'left')
85 check_eq(Editor_state.cursor1.line, 1, 'line')
86 check_eq(Editor_state.cursor1.pos, 4, 'pos') -- past end of line
87 end
89 function test_move_right_to_next_line()
90 App.screen.init{width=120, height=60}
91 Editor_state = edit.initialize_test_state()
92 Editor_state.lines = load_array{'abc', 'def'}
93 Text.redraw_all(Editor_state)
94 Editor_state.cursor1 = {line=1, pos=4} -- past end of line
95 edit.draw(Editor_state)
96 edit.run_after_keychord(Editor_state, 'right', 'right')
97 check_eq(Editor_state.cursor1.line, 2, 'line')
98 check_eq(Editor_state.cursor1.pos, 1, 'pos')
99 end
101 function test_move_to_start_of_word()
102 App.screen.init{width=120, height=60}
103 Editor_state = edit.initialize_test_state()
104 Editor_state.lines = load_array{'abc'}
105 Text.redraw_all(Editor_state)
106 Editor_state.cursor1 = {line=1, pos=3}
107 edit.draw(Editor_state)
108 edit.run_after_keychord(Editor_state, 'M-left', 'left')
109 check_eq(Editor_state.cursor1.pos, 1, 'check')
112 function test_move_to_start_of_previous_word()
113 App.screen.init{width=120, height=60}
114 Editor_state = edit.initialize_test_state()
115 Editor_state.lines = load_array{'abc def'}
116 Text.redraw_all(Editor_state)
117 Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
118 edit.draw(Editor_state)
119 edit.run_after_keychord(Editor_state, 'M-left', 'left')
120 check_eq(Editor_state.cursor1.pos, 1, 'check')
123 function test_skip_to_previous_word()
124 App.screen.init{width=120, height=60}
125 Editor_state = edit.initialize_test_state()
126 Editor_state.lines = load_array{'abc def'}
127 Text.redraw_all(Editor_state)
128 Editor_state.cursor1 = {line=1, pos=5} -- at the start of second word
129 edit.draw(Editor_state)
130 edit.run_after_keychord(Editor_state, 'M-left', 'left')
131 check_eq(Editor_state.cursor1.pos, 1, 'check')
134 function test_skip_past_tab_to_previous_word()
135 App.screen.init{width=120, height=60}
136 Editor_state = edit.initialize_test_state()
137 Editor_state.lines = load_array{'abc def\tghi'}
138 Text.redraw_all(Editor_state)
139 Editor_state.cursor1 = {line=1, pos=10} -- within third word
140 edit.draw(Editor_state)
141 edit.run_after_keychord(Editor_state, 'M-left', 'left')
142 check_eq(Editor_state.cursor1.pos, 9, 'check')
145 function test_skip_multiple_spaces_to_previous_word()
146 App.screen.init{width=120, height=60}
147 Editor_state = edit.initialize_test_state()
148 Editor_state.lines = load_array{'abc def'}
149 Text.redraw_all(Editor_state)
150 Editor_state.cursor1 = {line=1, pos=6} -- at the start of second word
151 edit.draw(Editor_state)
152 edit.run_after_keychord(Editor_state, 'M-left', 'left')
153 check_eq(Editor_state.cursor1.pos, 1, 'check')
156 function test_move_to_start_of_word_on_previous_line()
157 App.screen.init{width=120, height=60}
158 Editor_state = edit.initialize_test_state()
159 Editor_state.lines = load_array{'abc def', 'ghi'}
160 Text.redraw_all(Editor_state)
161 Editor_state.cursor1 = {line=2, pos=1}
162 edit.draw(Editor_state)
163 edit.run_after_keychord(Editor_state, 'M-left', 'left')
164 check_eq(Editor_state.cursor1.line, 1, 'line')
165 check_eq(Editor_state.cursor1.pos, 5, 'pos')
168 function test_move_past_end_of_word()
169 App.screen.init{width=120, height=60}
170 Editor_state = edit.initialize_test_state()
171 Editor_state.lines = load_array{'abc def'}
172 Text.redraw_all(Editor_state)
173 Editor_state.cursor1 = {line=1, pos=1}
174 edit.draw(Editor_state)
175 edit.run_after_keychord(Editor_state, 'M-right', 'right')
176 check_eq(Editor_state.cursor1.pos, 4, 'check')
179 function test_skip_to_next_word()
180 App.screen.init{width=120, height=60}
181 Editor_state = edit.initialize_test_state()
182 Editor_state.lines = load_array{'abc def'}
183 Text.redraw_all(Editor_state)
184 Editor_state.cursor1 = {line=1, pos=4} -- at the space between words
185 edit.draw(Editor_state)
186 edit.run_after_keychord(Editor_state, 'M-right', 'right')
187 check_eq(Editor_state.cursor1.pos, 8, 'check')
190 function test_skip_past_tab_to_next_word()
191 App.screen.init{width=120, height=60}
192 Editor_state = edit.initialize_test_state()
193 Editor_state.lines = load_array{'abc\tdef'}
194 Text.redraw_all(Editor_state)
195 Editor_state.cursor1 = {line=1, pos=1} -- at the space between words
196 edit.draw(Editor_state)
197 edit.run_after_keychord(Editor_state, 'M-right', 'right')
198 check_eq(Editor_state.cursor1.pos, 4, 'check')
201 function test_skip_multiple_spaces_to_next_word()
202 App.screen.init{width=120, height=60}
203 Editor_state = edit.initialize_test_state()
204 Editor_state.lines = load_array{'abc def'}
205 Text.redraw_all(Editor_state)
206 Editor_state.cursor1 = {line=1, pos=4} -- at the start of second word
207 edit.draw(Editor_state)
208 edit.run_after_keychord(Editor_state, 'M-right', 'right')
209 check_eq(Editor_state.cursor1.pos, 9, 'check')
212 function test_move_past_end_of_word_on_next_line()
213 App.screen.init{width=120, height=60}
214 Editor_state = edit.initialize_test_state()
215 Editor_state.lines = load_array{'abc def', 'ghi'}
216 Text.redraw_all(Editor_state)
217 Editor_state.cursor1 = {line=1, pos=8}
218 edit.draw(Editor_state)
219 edit.run_after_keychord(Editor_state, 'M-right', 'right')
220 check_eq(Editor_state.cursor1.line, 2, 'line')
221 check_eq(Editor_state.cursor1.pos, 4, 'pos')
224 function test_click_moves_cursor()
225 App.screen.init{width=50, height=60}
226 Editor_state = edit.initialize_test_state()
227 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
228 Text.redraw_all(Editor_state)
229 Editor_state.cursor1 = {line=1, pos=1}
230 Editor_state.screen_top1 = {line=1, pos=1}
231 Editor_state.selection1 = {}
232 edit.draw(Editor_state) -- populate line_cache.startpos for each line
233 edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
234 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
235 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
236 -- selection is empty to avoid perturbing future edits
237 check_nil(Editor_state.selection1.line, 'selection:line')
238 check_nil(Editor_state.selection1.pos, 'selection:pos')
241 function test_click_to_left_of_line()
242 -- display a line with the cursor in the middle
243 App.screen.init{width=50, height=80}
244 Editor_state = edit.initialize_test_state()
245 Editor_state.lines = load_array{'abc'}
246 Text.redraw_all(Editor_state)
247 Editor_state.cursor1 = {line=1, pos=3}
248 Editor_state.screen_top1 = {line=1, pos=1}
249 Editor_state.selection1 = {}
250 -- click to the left of the line
251 edit.draw(Editor_state)
252 edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)
253 -- cursor moves to start of line
254 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
255 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
256 check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
259 function test_click_takes_margins_into_account()
260 -- display two lines with cursor on one of them
261 App.screen.init{width=100, height=80}
262 Editor_state = edit.initialize_test_state()
263 Editor_state.left = 50 -- occupy only right side of screen
264 Editor_state.lines = load_array{'abc', 'def'}
265 Text.redraw_all(Editor_state)
266 Editor_state.cursor1 = {line=2, pos=1}
267 Editor_state.screen_top1 = {line=1, pos=1}
268 Editor_state.selection1 = {}
269 -- click on the other line
270 edit.draw(Editor_state)
271 edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
272 -- cursor moves
273 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
274 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
275 check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
278 function test_click_on_empty_line()
279 -- display two lines with the first one empty
280 App.screen.init{width=50, height=80}
281 Editor_state = edit.initialize_test_state()
282 Editor_state.lines = load_array{'', 'def'}
283 Text.redraw_all(Editor_state)
284 Editor_state.cursor1 = {line=2, pos=1}
285 Editor_state.screen_top1 = {line=1, pos=1}
286 Editor_state.selection1 = {}
287 -- click on the empty line
288 edit.draw(Editor_state)
289 edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
290 -- cursor moves
291 check_eq(Editor_state.cursor1.line, 1, 'cursor')
292 -- selection remains empty
293 check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
296 function test_click_below_all_lines()
297 -- display one line
298 App.screen.init{width=50, height=80}
299 Editor_state = edit.initialize_test_state()
300 Editor_state.lines = load_array{'abc'}
301 Text.redraw_all(Editor_state)
302 Editor_state.cursor1 = {line=1, pos=1}
303 Editor_state.screen_top1 = {line=1, pos=1}
304 Editor_state.selection1 = {}
305 -- click below first line
306 edit.draw(Editor_state)
307 edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+50, 1)
308 -- cursor doesn't move
309 check_eq(Editor_state.cursor1.line, 1, 'cursor')
310 -- selection remains empty
311 check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
314 function test_draw_text()
315 App.screen.init{width=120, height=60}
316 Editor_state = edit.initialize_test_state()
317 Editor_state.lines = load_array{'abc', 'def', 'ghi'}
318 Text.redraw_all(Editor_state)
319 Editor_state.cursor1 = {line=1, pos=1}
320 Editor_state.screen_top1 = {line=1, pos=1}
321 edit.draw(Editor_state)
322 local y = Editor_state.top
323 App.screen.check(y, 'abc', 'screen:1')
324 y = y + Editor_state.line_height
325 App.screen.check(y, 'def', 'screen:2')
326 y = y + Editor_state.line_height
327 App.screen.check(y, 'ghi', 'screen:3')
330 function test_draw_wrapping_text()
331 App.screen.init{width=50, height=60}
332 Editor_state = edit.initialize_test_state()
333 Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
334 Text.redraw_all(Editor_state)
335 Editor_state.cursor1 = {line=1, pos=1}
336 Editor_state.screen_top1 = {line=1, pos=1}
337 edit.draw(Editor_state)
338 local y = Editor_state.top
339 App.screen.check(y, 'abc', 'screen:1')
340 y = y + Editor_state.line_height
341 App.screen.check(y, 'de', 'screen:2')
342 y = y + Editor_state.line_height
343 App.screen.check(y, 'fgh', 'screen:3')
346 function test_draw_word_wrapping_text()
347 App.screen.init{width=60, height=60}
348 Editor_state = edit.initialize_test_state()
349 Editor_state.lines = load_array{'abc def ghi', 'jkl'}
350 Text.redraw_all(Editor_state)
351 Editor_state.cursor1 = {line=1, pos=1}
352 Editor_state.screen_top1 = {line=1, pos=1}
353 edit.draw(Editor_state)
354 local y = Editor_state.top
355 App.screen.check(y, 'abc ', 'screen:1')
356 y = y + Editor_state.line_height
357 App.screen.check(y, 'def ', 'screen:2')
358 y = y + Editor_state.line_height
359 App.screen.check(y, 'ghi', 'screen:3')
362 function test_click_on_wrapping_line()
363 -- display two screen lines with cursor on one of them
364 App.screen.init{width=50, height=80}
365 Editor_state = edit.initialize_test_state()
366 Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
367 Text.redraw_all(Editor_state)
368 Editor_state.cursor1 = {line=1, pos=20}
369 Editor_state.screen_top1 = {line=1, pos=1}
370 -- click on the other line
371 edit.draw(Editor_state)
372 edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
373 -- cursor moves
374 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
375 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
376 check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
379 function test_click_on_wrapping_line_takes_margins_into_account()
380 -- display two screen lines with cursor on one of them
381 App.screen.init{width=100, height=80}
382 Editor_state = edit.initialize_test_state()
383 Editor_state.left = 50 -- occupy only right side of screen
384 Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}
385 Text.redraw_all(Editor_state)
386 Editor_state.cursor1 = {line=1, pos=20}
387 Editor_state.screen_top1 = {line=1, pos=1}
388 -- click on the other line
389 edit.draw(Editor_state)
390 edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
391 -- cursor moves
392 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
393 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
394 check_nil(Editor_state.selection1.line, 'selection is empty to avoid perturbing future edits')
397 function test_draw_text_wrapping_within_word()
398 -- arrange a screen line that needs to be split within a word
399 App.screen.init{width=60, height=60}
400 Editor_state = edit.initialize_test_state()
401 Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}
402 Text.redraw_all(Editor_state)
403 Editor_state.cursor1 = {line=1, pos=1}
404 Editor_state.screen_top1 = {line=1, pos=1}
405 edit.draw(Editor_state)
406 local y = Editor_state.top
407 App.screen.check(y, 'abcd ', 'screen:1')
408 y = y + Editor_state.line_height
409 App.screen.check(y, 'e fgh', 'screen:2')
410 y = y + Editor_state.line_height
411 App.screen.check(y, 'ijk', 'screen:3')
414 function test_draw_wrapping_text_containing_non_ascii()
415 -- draw a long line containing non-ASCII
416 App.screen.init{width=60, height=60}
417 Editor_state = edit.initialize_test_state()
418 Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostrophe
419 Text.redraw_all(Editor_state)
420 Editor_state.cursor1 = {line=1, pos=1}
421 Editor_state.screen_top1 = {line=1, pos=1}
422 edit.draw(Editor_state)
423 local y = Editor_state.top
424 App.screen.check(y, 'mad', 'screen:1')
425 y = y + Editor_state.line_height
426 App.screen.check(y, 'am I', 'screen:2')
427 y = y + Editor_state.line_height
428 App.screen.check(y, '’m a', 'screen:3')
431 function test_click_past_end_of_screen_line()
432 -- display a wrapping line
433 App.screen.init{width=75, height=80}
434 Editor_state = edit.initialize_test_state()
435 -- 12345678901234
436 Editor_state.lines = load_array{"madam I'm adam"}
437 Text.redraw_all(Editor_state)
438 Editor_state.cursor1 = {line=1, pos=1}
439 Editor_state.screen_top1 = {line=1, pos=1}
440 edit.draw(Editor_state)
441 local y = Editor_state.top
442 App.screen.check(y, 'madam ', 'baseline/screen:1')
443 y = y + Editor_state.line_height
444 App.screen.check(y, "I'm ad", 'baseline/screen:2')
445 y = y + Editor_state.line_height
446 -- click past end of second screen line
447 edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
448 -- cursor moves to end of screen line (one more than final character shown)
449 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
450 check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')
453 function test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()
454 -- display a wrapping line from its second screen line
455 App.screen.init{width=75, height=80}
456 Editor_state = edit.initialize_test_state()
457 -- 12345678901234
458 Editor_state.lines = load_array{"madam I'm adam"}
459 Text.redraw_all(Editor_state)
460 Editor_state.cursor1 = {line=1, pos=8}
461 Editor_state.screen_top1 = {line=1, pos=7}
462 edit.draw(Editor_state)
463 local y = Editor_state.top
464 App.screen.check(y, "I'm ad", 'baseline/screen:2')
465 y = y + Editor_state.line_height
466 -- click past end of second screen line
467 edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
468 -- cursor moves to end of screen line (one more than final character shown)
469 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
470 check_eq(Editor_state.cursor1.pos, 13, 'cursor:pos')
473 function test_click_past_end_of_wrapping_line()
474 -- display a wrapping line
475 App.screen.init{width=75, height=80}
476 Editor_state = edit.initialize_test_state()
477 -- 12345678901234
478 Editor_state.lines = load_array{"madam I'm adam"}
479 Text.redraw_all(Editor_state)
480 Editor_state.cursor1 = {line=1, pos=1}
481 Editor_state.screen_top1 = {line=1, pos=1}
482 edit.draw(Editor_state)
483 local y = Editor_state.top
484 App.screen.check(y, 'madam ', 'baseline/screen:1')
485 y = y + Editor_state.line_height
486 App.screen.check(y, "I'm ad", 'baseline/screen:2')
487 y = y + Editor_state.line_height
488 App.screen.check(y, 'am', 'baseline/screen:3')
489 y = y + Editor_state.line_height
490 -- click past the end of it
491 edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
492 -- cursor moves to end of line
493 check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
496 function test_click_past_end_of_wrapping_line_containing_non_ascii()
497 -- display a wrapping line containing non-ASCII
498 App.screen.init{width=75, height=80}
499 Editor_state = edit.initialize_test_state()
500 -- 12345678901234
501 Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostrophe
502 Text.redraw_all(Editor_state)
503 Editor_state.cursor1 = {line=1, pos=1}
504 Editor_state.screen_top1 = {line=1, pos=1}
505 edit.draw(Editor_state)
506 local y = Editor_state.top
507 App.screen.check(y, 'madam ', 'baseline/screen:1')
508 y = y + Editor_state.line_height
509 App.screen.check(y, 'I’m ad', 'baseline/screen:2')
510 y = y + Editor_state.line_height
511 App.screen.check(y, 'am', 'baseline/screen:3')
512 y = y + Editor_state.line_height
513 -- click past the end of it
514 edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
515 -- cursor moves to end of line
516 check_eq(Editor_state.cursor1.pos, 15, 'cursor') -- one more than the number of UTF-8 code-points
519 function test_click_past_end_of_word_wrapping_line()
520 -- display a long line wrapping at a word boundary on a screen of more realistic length
521 App.screen.init{width=160, height=80}
522 Editor_state = edit.initialize_test_state()
523 -- 0 1 2
524 -- 123456789012345678901
525 Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}
526 Text.redraw_all(Editor_state)
527 Editor_state.cursor1 = {line=1, pos=1}
528 Editor_state.screen_top1 = {line=1, pos=1}
529 edit.draw(Editor_state)
530 local y = Editor_state.top
531 App.screen.check(y, 'the quick brown fox ', 'baseline/screen:1')
532 y = y + Editor_state.line_height
533 -- click past the end of the screen line
534 edit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)
535 -- cursor moves to end of screen line (one more than final character shown)
536 check_eq(Editor_state.cursor1.pos, 21, 'cursor')
539 function test_select_text()
540 -- display a line of text
541 App.screen.init{width=75, height=80}
542 Editor_state = edit.initialize_test_state()
543 Editor_state.lines = load_array{'abc def'}
544 Text.redraw_all(Editor_state)
545 Editor_state.cursor1 = {line=1, pos=1}
546 Editor_state.screen_top1 = {line=1, pos=1}
547 edit.draw(Editor_state)
548 -- select a letter
549 App.fake_key_press('lshift')
550 edit.run_after_keychord(Editor_state, 'S-right', 'right')
551 App.fake_key_release('lshift')
552 edit.key_release(Editor_state, 'lshift')
553 -- selection persists even after shift is released
554 check_eq(Editor_state.selection1.line, 1, 'selection:line')
555 check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
556 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
557 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
560 function test_cursor_movement_without_shift_resets_selection()
561 -- display a line of text with some part selected
562 App.screen.init{width=75, height=80}
563 Editor_state = edit.initialize_test_state()
564 Editor_state.lines = load_array{'abc'}
565 Text.redraw_all(Editor_state)
566 Editor_state.cursor1 = {line=1, pos=1}
567 Editor_state.selection1 = {line=1, pos=2}
568 Editor_state.screen_top1 = {line=1, pos=1}
569 edit.draw(Editor_state)
570 -- press an arrow key without shift
571 edit.run_after_keychord(Editor_state, 'right', 'right')
572 -- no change to data, selection is reset
573 check_nil(Editor_state.selection1.line, 'check')
574 check_eq(Editor_state.lines[1].data, 'abc', 'data')
577 function test_edit_deletes_selection()
578 -- display a line of text with some part selected
579 App.screen.init{width=75, height=80}
580 Editor_state = edit.initialize_test_state()
581 Editor_state.lines = load_array{'abc'}
582 Text.redraw_all(Editor_state)
583 Editor_state.cursor1 = {line=1, pos=1}
584 Editor_state.selection1 = {line=1, pos=2}
585 Editor_state.screen_top1 = {line=1, pos=1}
586 edit.draw(Editor_state)
587 -- press a key
588 edit.run_after_text_input(Editor_state, 'x')
589 -- selected text is deleted and replaced with the key
590 check_eq(Editor_state.lines[1].data, 'xbc', 'check')
593 function test_edit_with_shift_key_deletes_selection()
594 -- display a line of text with some part selected
595 App.screen.init{width=75, height=80}
596 Editor_state = edit.initialize_test_state()
597 Editor_state.lines = load_array{'abc'}
598 Text.redraw_all(Editor_state)
599 Editor_state.cursor1 = {line=1, pos=1}
600 Editor_state.selection1 = {line=1, pos=2}
601 Editor_state.screen_top1 = {line=1, pos=1}
602 edit.draw(Editor_state)
603 -- mimic precise keypresses for a capital letter
604 App.fake_key_press('lshift')
605 edit.keychord_press(Editor_state, 'd', 'd')
606 edit.text_input(Editor_state, 'D')
607 edit.key_release(Editor_state, 'd')
608 App.fake_key_release('lshift')
609 -- selected text is deleted and replaced with the key
610 check_nil(Editor_state.selection1.line, 'check')
611 check_eq(Editor_state.lines[1].data, 'Dbc', 'data')
614 function test_copy_does_not_reset_selection()
615 -- display a line of text with a selection
616 App.screen.init{width=75, height=80}
617 Editor_state = edit.initialize_test_state()
618 Editor_state.lines = load_array{'abc'}
619 Text.redraw_all(Editor_state)
620 Editor_state.cursor1 = {line=1, pos=1}
621 Editor_state.selection1 = {line=1, pos=2}
622 Editor_state.screen_top1 = {line=1, pos=1}
623 edit.draw(Editor_state)
624 -- copy selection
625 edit.run_after_keychord(Editor_state, 'C-c', 'c')
626 check_eq(App.clipboard, 'a', 'clipboard')
627 -- selection is reset since shift key is not pressed
628 check(Editor_state.selection1.line, 'check')
631 function test_cut()
632 -- display a line of text with some part selected
633 App.screen.init{width=75, height=80}
634 Editor_state = edit.initialize_test_state()
635 Editor_state.lines = load_array{'abc'}
636 Text.redraw_all(Editor_state)
637 Editor_state.cursor1 = {line=1, pos=1}
638 Editor_state.selection1 = {line=1, pos=2}
639 Editor_state.screen_top1 = {line=1, pos=1}
640 edit.draw(Editor_state)
641 -- press a key
642 edit.run_after_keychord(Editor_state, 'C-x', 'x')
643 check_eq(App.clipboard, 'a', 'clipboard')
644 -- selected text is deleted
645 check_eq(Editor_state.lines[1].data, 'bc', 'data')
648 function test_paste_replaces_selection()
649 -- display a line of text with a selection
650 App.screen.init{width=75, height=80}
651 Editor_state = edit.initialize_test_state()
652 Editor_state.lines = load_array{'abc', 'def'}
653 Text.redraw_all(Editor_state)
654 Editor_state.cursor1 = {line=2, pos=1}
655 Editor_state.selection1 = {line=1, pos=1}
656 Editor_state.screen_top1 = {line=1, pos=1}
657 edit.draw(Editor_state)
658 -- set clipboard
659 App.clipboard = 'xyz'
660 -- paste selection
661 edit.run_after_keychord(Editor_state, 'C-v', 'v')
662 -- selection is reset since shift key is not pressed
663 -- selection includes the newline, so it's also deleted
664 check_eq(Editor_state.lines[1].data, 'xyzdef', 'check')
667 function test_deleting_selection_may_scroll()
668 -- display lines 2/3/4
669 App.screen.init{width=120, height=60}
670 Editor_state = edit.initialize_test_state()
671 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
672 Text.redraw_all(Editor_state)
673 Editor_state.cursor1 = {line=3, pos=2}
674 Editor_state.screen_top1 = {line=2, pos=1}
675 edit.draw(Editor_state)
676 local y = Editor_state.top
677 App.screen.check(y, 'def', 'baseline/screen:1')
678 y = y + Editor_state.line_height
679 App.screen.check(y, 'ghi', 'baseline/screen:2')
680 y = y + Editor_state.line_height
681 App.screen.check(y, 'jkl', 'baseline/screen:3')
682 -- set up a selection starting above the currently displayed page
683 Editor_state.selection1 = {line=1, pos=2}
684 -- delete selection
685 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
686 -- page scrolls up
687 check_eq(Editor_state.screen_top1.line, 1, 'check')
688 check_eq(Editor_state.lines[1].data, 'ahi', 'data')
691 function test_edit_wrapping_text()
692 App.screen.init{width=50, height=60}
693 Editor_state = edit.initialize_test_state()
694 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
695 Text.redraw_all(Editor_state)
696 Editor_state.cursor1 = {line=2, pos=4}
697 Editor_state.screen_top1 = {line=1, pos=1}
698 edit.draw(Editor_state)
699 edit.run_after_text_input(Editor_state, 'g')
700 local y = Editor_state.top
701 App.screen.check(y, 'abc', 'screen:1')
702 y = y + Editor_state.line_height
703 App.screen.check(y, 'de', 'screen:2')
704 y = y + Editor_state.line_height
705 App.screen.check(y, 'fg', 'screen:3')
708 function test_insert_newline()
709 -- display a few lines
710 App.screen.init{width=Editor_state.left+30, height=60}
711 Editor_state = edit.initialize_test_state()
712 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
713 Text.redraw_all(Editor_state)
714 Editor_state.cursor1 = {line=1, pos=2}
715 Editor_state.screen_top1 = {line=1, pos=1}
716 edit.draw(Editor_state)
717 local y = Editor_state.top
718 App.screen.check(y, 'abc', 'baseline/screen:1')
719 y = y + Editor_state.line_height
720 App.screen.check(y, 'def', 'baseline/screen:2')
721 y = y + Editor_state.line_height
722 App.screen.check(y, 'ghi', 'baseline/screen:3')
723 -- hitting the enter key splits the line
724 edit.run_after_keychord(Editor_state, 'return', 'return')
725 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
726 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
727 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
728 y = Editor_state.top
729 App.screen.check(y, 'a', 'screen:1')
730 y = y + Editor_state.line_height
731 App.screen.check(y, 'bc', 'screen:2')
732 y = y + Editor_state.line_height
733 App.screen.check(y, 'def', 'screen:3')
736 function test_insert_newline_at_start_of_line()
737 -- display a line
738 App.screen.init{width=Editor_state.left+30, height=60}
739 Editor_state = edit.initialize_test_state()
740 Editor_state.lines = load_array{'abc'}
741 Text.redraw_all(Editor_state)
742 Editor_state.cursor1 = {line=1, pos=1}
743 Editor_state.screen_top1 = {line=1, pos=1}
744 -- hitting the enter key splits the line
745 edit.run_after_keychord(Editor_state, 'return', 'return')
746 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
747 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
748 check_eq(Editor_state.lines[1].data, '', 'data:1')
749 check_eq(Editor_state.lines[2].data, 'abc', 'data:2')
752 function test_insert_from_clipboard()
753 -- display a few lines
754 App.screen.init{width=Editor_state.left+30, height=60}
755 Editor_state = edit.initialize_test_state()
756 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
757 Text.redraw_all(Editor_state)
758 Editor_state.cursor1 = {line=1, pos=2}
759 Editor_state.screen_top1 = {line=1, pos=1}
760 edit.draw(Editor_state)
761 local y = Editor_state.top
762 App.screen.check(y, 'abc', 'baseline/screen:1')
763 y = y + Editor_state.line_height
764 App.screen.check(y, 'def', 'baseline/screen:2')
765 y = y + Editor_state.line_height
766 App.screen.check(y, 'ghi', 'baseline/screen:3')
767 -- paste some text including a newline, check that new line is created
768 App.clipboard = 'xy\nz'
769 edit.run_after_keychord(Editor_state, 'C-v', 'v')
770 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
771 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
772 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
773 y = Editor_state.top
774 App.screen.check(y, 'axy', 'screen:1')
775 y = y + Editor_state.line_height
776 App.screen.check(y, 'zbc', 'screen:2')
777 y = y + Editor_state.line_height
778 App.screen.check(y, 'def', 'screen:3')
781 function test_select_text_using_mouse()
782 App.screen.init{width=50, height=60}
783 Editor_state = edit.initialize_test_state()
784 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
785 Text.redraw_all(Editor_state)
786 Editor_state.cursor1 = {line=1, pos=1}
787 Editor_state.screen_top1 = {line=1, pos=1}
788 Editor_state.selection1 = {}
789 edit.draw(Editor_state) -- populate line_cache.startpos for each line
790 -- press and hold on first location
791 edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
792 -- drag and release somewhere else
793 edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
794 check_eq(Editor_state.selection1.line, 1, 'selection:line')
795 check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
796 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
797 check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
800 function test_select_text_using_mouse_starting_above_text()
801 App.screen.init{width=50, height=60}
802 Editor_state = edit.initialize_test_state()
803 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
804 Text.redraw_all(Editor_state)
805 Editor_state.cursor1 = {line=1, pos=1}
806 Editor_state.screen_top1 = {line=1, pos=1}
807 Editor_state.selection1 = {}
808 edit.draw(Editor_state) -- populate line_cache.startpos for each line
809 -- press mouse above first line of text
810 edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
811 check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
812 check_eq(Editor_state.selection1.line, 1, 'selection:line')
813 check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
816 function test_select_text_using_mouse_starting_above_text_wrapping_line()
817 -- first screen line starts in the middle of a line
818 App.screen.init{width=50, height=60}
819 Editor_state = edit.initialize_test_state()
820 Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}
821 Text.redraw_all(Editor_state)
822 Editor_state.cursor1 = {line=2, pos=5}
823 Editor_state.screen_top1 = {line=2, pos=3}
824 -- press mouse above first line of text
825 edit.draw(Editor_state)
826 edit.run_after_mouse_press(Editor_state, Editor_state.left+8,5, 1)
827 -- selection is at screen top
828 check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
829 check_eq(Editor_state.selection1.line, 2, 'selection:line')
830 check_eq(Editor_state.selection1.pos, 3, 'selection:pos')
833 function test_select_text_using_mouse_starting_below_text()
834 -- I'd like to test what happens when a mouse click is below some page of
835 -- text, potentially even in the middle of a line.
836 -- However, it's brittle to set up a text line boundary just right.
837 -- So I'm going to just check things below the bottom of the final line of
838 -- text when it's in the middle of the screen.
839 -- final screen line ends in the middle of screen
840 App.screen.init{width=50, height=60}
841 Editor_state = edit.initialize_test_state()
842 Editor_state.lines = load_array{'abcde'}
843 Text.redraw_all(Editor_state)
844 Editor_state.cursor1 = {line=1, pos=1}
845 Editor_state.screen_top1 = {line=1, pos=1}
846 edit.draw(Editor_state)
847 local y = Editor_state.top
848 App.screen.check(y, 'ab', 'baseline:screen:1')
849 y = y + Editor_state.line_height
850 App.screen.check(y, 'cde', 'baseline:screen:2')
851 -- press mouse above first line of text
852 edit.run_after_mouse_press(Editor_state, 5,App.screen.height-5, 1)
853 -- selection is past bottom-most text in screen
854 check(Editor_state.selection1.line ~= nil, 'selection:line-not-nil')
855 check_eq(Editor_state.selection1.line, 1, 'selection:line')
856 check_eq(Editor_state.selection1.pos, 6, 'selection:pos')
859 function test_select_text_using_mouse_and_shift()
860 App.screen.init{width=50, height=60}
861 Editor_state = edit.initialize_test_state()
862 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
863 Text.redraw_all(Editor_state)
864 Editor_state.cursor1 = {line=1, pos=1}
865 Editor_state.screen_top1 = {line=1, pos=1}
866 Editor_state.selection1 = {}
867 edit.draw(Editor_state) -- populate line_cache.startpos for each line
868 -- click on first location
869 edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
870 edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
871 -- hold down shift and click somewhere else
872 App.fake_key_press('lshift')
873 edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
874 edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
875 App.fake_key_release('lshift')
876 check_eq(Editor_state.selection1.line, 1, 'selection:line')
877 check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
878 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
879 check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
882 function test_select_text_repeatedly_using_mouse_and_shift()
883 App.screen.init{width=50, height=60}
884 Editor_state = edit.initialize_test_state()
885 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
886 Text.redraw_all(Editor_state)
887 Text.redraw_all(Editor_state)
888 Editor_state.cursor1 = {line=1, pos=1}
889 Editor_state.screen_top1 = {line=1, pos=1}
890 Editor_state.selection1 = {}
891 edit.draw(Editor_state) -- populate line_cache.startpos for each line
892 -- click on first location
893 edit.run_after_mouse_press(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
894 edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)
895 -- hold down shift and click on a second location
896 App.fake_key_press('lshift')
897 edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
898 edit.run_after_mouse_release(Editor_state, Editor_state.left+20,Editor_state.top+Editor_state.line_height+5, 1)
899 -- hold down shift and click at a third location
900 App.fake_key_press('lshift')
901 edit.run_after_mouse_press(Editor_state, Editor_state.left+20,Editor_state.top+5, 1)
902 edit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+Editor_state.line_height+5, 1)
903 App.fake_key_release('lshift')
904 -- selection is between first and third location. forget the second location, not the first.
905 check_eq(Editor_state.selection1.line, 1, 'selection:line')
906 check_eq(Editor_state.selection1.pos, 2, 'selection:pos')
907 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
908 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
911 function test_select_all_text()
912 -- display a single line of text
913 App.screen.init{width=75, height=80}
914 Editor_state = edit.initialize_test_state()
915 Editor_state.lines = load_array{'abc def'}
916 Text.redraw_all(Editor_state)
917 Editor_state.cursor1 = {line=1, pos=1}
918 Editor_state.screen_top1 = {line=1, pos=1}
919 edit.draw(Editor_state)
920 -- select all
921 App.fake_key_press('lctrl')
922 edit.run_after_keychord(Editor_state, 'C-a', 'a')
923 App.fake_key_release('lctrl')
924 edit.key_release(Editor_state, 'lctrl')
925 -- selection
926 check_eq(Editor_state.selection1.line, 1, 'selection:line')
927 check_eq(Editor_state.selection1.pos, 1, 'selection:pos')
928 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
929 check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')
932 function test_cut_without_selection()
933 -- display a few lines
934 App.screen.init{width=Editor_state.left+30, height=60}
935 Editor_state = edit.initialize_test_state()
936 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
937 Text.redraw_all(Editor_state)
938 Editor_state.cursor1 = {line=1, pos=2}
939 Editor_state.screen_top1 = {line=1, pos=1}
940 Editor_state.selection1 = {}
941 edit.draw(Editor_state)
942 -- try to cut without selecting text
943 edit.run_after_keychord(Editor_state, 'C-x', 'x')
944 -- no crash
945 check_nil(Editor_state.selection1.line, 'check')
948 function test_pagedown()
949 App.screen.init{width=120, height=45}
950 Editor_state = edit.initialize_test_state()
951 Editor_state.lines = load_array{'abc', 'def', 'ghi'}
952 Text.redraw_all(Editor_state)
953 Editor_state.cursor1 = {line=1, pos=1}
954 Editor_state.screen_top1 = {line=1, pos=1}
955 -- initially the first two lines are displayed
956 edit.draw(Editor_state)
957 local y = Editor_state.top
958 App.screen.check(y, 'abc', 'baseline/screen:1')
959 y = y + Editor_state.line_height
960 App.screen.check(y, 'def', 'baseline/screen:2')
961 -- after pagedown the bottom line becomes the top
962 edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
963 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
964 check_eq(Editor_state.cursor1.line, 2, 'cursor')
965 y = Editor_state.top
966 App.screen.check(y, 'def', 'screen:1')
967 y = y + Editor_state.line_height
968 App.screen.check(y, 'ghi', 'screen:2')
971 function test_pagedown_can_start_from_middle_of_long_wrapping_line()
972 -- draw a few lines starting from a very long wrapping line
973 App.screen.init{width=Editor_state.left+30, height=60}
974 Editor_state = edit.initialize_test_state()
975 Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu vwx yza bcd efg hij', 'XYZ'}
976 Text.redraw_all(Editor_state)
977 Editor_state.cursor1 = {line=1, pos=2}
978 Editor_state.screen_top1 = {line=1, pos=1}
979 edit.draw(Editor_state)
980 local y = Editor_state.top
981 App.screen.check(y, 'abc ', 'baseline/screen:1')
982 y = y + Editor_state.line_height
983 App.screen.check(y, 'def ', 'baseline/screen:2')
984 y = y + Editor_state.line_height
985 App.screen.check(y, 'ghi ', 'baseline/screen:3')
986 -- after pagedown we scroll down the very long wrapping line
987 edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
988 check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
989 check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')
990 y = Editor_state.top
991 App.screen.check(y, 'ghi ', 'screen:1')
992 y = y + Editor_state.line_height
993 App.screen.check(y, 'jkl ', 'screen:2')
994 y = y + Editor_state.line_height
995 if Version == '12.0' then
996 -- HACK: Maybe v12.0 uses a different font? Strange that it only causes
997 -- issues in a couple of places.
998 -- We'll need to rethink our tests if issues like this start to multiply.
999 App.screen.check(y, 'mno ', 'screen:3')
1000 else
1001 App.screen.check(y, 'mn', 'screen:3')
1005 function test_pagedown_never_moves_up()
1006 -- draw the final screen line of a wrapping line
1007 App.screen.init{width=Editor_state.left+30, height=60}
1008 Editor_state = edit.initialize_test_state()
1009 Editor_state.lines = load_array{'abc def ghi'}
1010 Text.redraw_all(Editor_state)
1011 Editor_state.cursor1 = {line=1, pos=9}
1012 Editor_state.screen_top1 = {line=1, pos=9}
1013 edit.draw(Editor_state)
1014 -- pagedown makes no change
1015 edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
1016 check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
1017 check_eq(Editor_state.screen_top1.pos, 9, 'screen_top:pos')
1020 function test_down_arrow_moves_cursor()
1021 App.screen.init{width=120, height=60}
1022 Editor_state = edit.initialize_test_state()
1023 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1024 Text.redraw_all(Editor_state)
1025 Editor_state.cursor1 = {line=1, pos=1}
1026 Editor_state.screen_top1 = {line=1, pos=1}
1027 -- initially the first three lines are displayed
1028 edit.draw(Editor_state)
1029 local y = Editor_state.top
1030 App.screen.check(y, 'abc', 'baseline/screen:1')
1031 y = y + Editor_state.line_height
1032 App.screen.check(y, 'def', 'baseline/screen:2')
1033 y = y + Editor_state.line_height
1034 App.screen.check(y, 'ghi', 'baseline/screen:3')
1035 -- after hitting the down arrow, the cursor moves down by 1 line
1036 edit.run_after_keychord(Editor_state, 'down', 'down')
1037 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1038 check_eq(Editor_state.cursor1.line, 2, 'cursor')
1039 -- the screen is unchanged
1040 y = Editor_state.top
1041 App.screen.check(y, 'abc', 'screen:1')
1042 y = y + Editor_state.line_height
1043 App.screen.check(y, 'def', 'screen:2')
1044 y = y + Editor_state.line_height
1045 App.screen.check(y, 'ghi', 'screen:3')
1048 function test_down_arrow_scrolls_down_by_one_line()
1049 -- display the first three lines with the cursor on the bottom line
1050 App.screen.init{width=120, height=60}
1051 Editor_state = edit.initialize_test_state()
1052 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1053 Text.redraw_all(Editor_state)
1054 Editor_state.cursor1 = {line=3, pos=1}
1055 Editor_state.screen_top1 = {line=1, pos=1}
1056 edit.draw(Editor_state)
1057 local y = Editor_state.top
1058 App.screen.check(y, 'abc', 'baseline/screen:1')
1059 y = y + Editor_state.line_height
1060 App.screen.check(y, 'def', 'baseline/screen:2')
1061 y = y + Editor_state.line_height
1062 App.screen.check(y, 'ghi', 'baseline/screen:3')
1063 -- after hitting the down arrow the screen scrolls down by one line
1064 edit.run_after_keychord(Editor_state, 'down', 'down')
1065 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1066 check_eq(Editor_state.cursor1.line, 4, 'cursor')
1067 y = Editor_state.top
1068 App.screen.check(y, 'def', 'screen:1')
1069 y = y + Editor_state.line_height
1070 App.screen.check(y, 'ghi', 'screen:2')
1071 y = y + Editor_state.line_height
1072 App.screen.check(y, 'jkl', 'screen:3')
1075 function test_down_arrow_scrolls_down_by_one_screen_line()
1076 -- display the first three lines with the cursor on the bottom line
1077 App.screen.init{width=Editor_state.left+30, height=60}
1078 Editor_state = edit.initialize_test_state()
1079 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1080 Text.redraw_all(Editor_state)
1081 Editor_state.cursor1 = {line=3, pos=1}
1082 Editor_state.screen_top1 = {line=1, pos=1}
1083 edit.draw(Editor_state)
1084 local y = Editor_state.top
1085 App.screen.check(y, 'abc', 'baseline/screen:1')
1086 y = y + Editor_state.line_height
1087 App.screen.check(y, 'def', 'baseline/screen:2')
1088 y = y + Editor_state.line_height
1089 App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
1090 -- after hitting the down arrow the screen scrolls down by one line
1091 edit.run_after_keychord(Editor_state, 'down', 'down')
1092 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1093 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1094 check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
1095 y = Editor_state.top
1096 App.screen.check(y, 'def', 'screen:1')
1097 y = y + Editor_state.line_height
1098 App.screen.check(y, 'ghi ', 'screen:2')
1099 y = y + Editor_state.line_height
1100 App.screen.check(y, 'jkl', 'screen:3')
1103 function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word()
1104 -- display the first three lines with the cursor on the bottom line
1105 App.screen.init{width=Editor_state.left+30, height=60}
1106 Editor_state = edit.initialize_test_state()
1107 Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
1108 Text.redraw_all(Editor_state)
1109 Editor_state.cursor1 = {line=3, pos=1}
1110 Editor_state.screen_top1 = {line=1, pos=1}
1111 edit.draw(Editor_state)
1112 local y = Editor_state.top
1113 App.screen.check(y, 'abc', 'baseline/screen:1')
1114 y = y + Editor_state.line_height
1115 App.screen.check(y, 'def', 'baseline/screen:2')
1116 y = y + Editor_state.line_height
1117 App.screen.check(y, 'ghij', 'baseline/screen:3')
1118 -- after hitting the down arrow the screen scrolls down by one line
1119 edit.run_after_keychord(Editor_state, 'down', 'down')
1120 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1121 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1122 check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
1123 y = Editor_state.top
1124 App.screen.check(y, 'def', 'screen:1')
1125 y = y + Editor_state.line_height
1126 App.screen.check(y, 'ghij', 'screen:2')
1127 y = y + Editor_state.line_height
1128 App.screen.check(y, 'kl', 'screen:3')
1131 function test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()
1132 App.screen.init{width=Editor_state.left+30, height=60}
1133 Editor_state = edit.initialize_test_state()
1134 Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}
1135 Text.redraw_all(Editor_state)
1136 Editor_state.cursor1 = {line=3, pos=1}
1137 Editor_state.screen_top1 = {line=1, pos=1}
1138 edit.draw(Editor_state)
1139 local y = Editor_state.top
1140 App.screen.check(y, 'abc', 'baseline/screen:1')
1141 y = y + Editor_state.line_height
1142 App.screen.check(y, 'def', 'baseline/screen:2')
1143 y = y + Editor_state.line_height
1144 App.screen.check(y, 'ghij', 'baseline/screen:3')
1145 -- after hitting pagedown the screen scrolls down to start of a long line
1146 edit.run_after_keychord(Editor_state, 'pagedown', 'pagedown')
1147 check_eq(Editor_state.screen_top1.line, 3, 'baseline2/screen_top')
1148 check_eq(Editor_state.cursor1.line, 3, 'baseline2/cursor:line')
1149 check_eq(Editor_state.cursor1.pos, 1, 'baseline2/cursor:pos')
1150 -- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
1151 edit.run_after_keychord(Editor_state, 'down', 'down')
1152 check_eq(Editor_state.screen_top1.line, 3, 'screen_top')
1153 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1154 check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
1155 y = Editor_state.top
1156 App.screen.check(y, 'ghij', 'screen:1')
1157 y = y + Editor_state.line_height
1158 App.screen.check(y, 'kl', 'screen:2')
1159 y = y + Editor_state.line_height
1160 App.screen.check(y, 'mno', 'screen:3')
1163 function test_up_arrow_moves_cursor()
1164 -- display the first 3 lines with the cursor on the bottom line
1165 App.screen.init{width=120, height=60}
1166 Editor_state = edit.initialize_test_state()
1167 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1168 Text.redraw_all(Editor_state)
1169 Editor_state.cursor1 = {line=3, pos=1}
1170 Editor_state.screen_top1 = {line=1, pos=1}
1171 edit.draw(Editor_state)
1172 local y = Editor_state.top
1173 App.screen.check(y, 'abc', 'baseline/screen:1')
1174 y = y + Editor_state.line_height
1175 App.screen.check(y, 'def', 'baseline/screen:2')
1176 y = y + Editor_state.line_height
1177 App.screen.check(y, 'ghi', 'baseline/screen:3')
1178 -- after hitting the up arrow the cursor moves up by 1 line
1179 edit.run_after_keychord(Editor_state, 'up', 'up')
1180 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1181 check_eq(Editor_state.cursor1.line, 2, 'cursor')
1182 -- the screen is unchanged
1183 y = Editor_state.top
1184 App.screen.check(y, 'abc', 'screen:1')
1185 y = y + Editor_state.line_height
1186 App.screen.check(y, 'def', 'screen:2')
1187 y = y + Editor_state.line_height
1188 App.screen.check(y, 'ghi', 'screen:3')
1191 function test_up_arrow_scrolls_up_by_one_line()
1192 -- display the lines 2/3/4 with the cursor on line 2
1193 App.screen.init{width=120, height=60}
1194 Editor_state = edit.initialize_test_state()
1195 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1196 Text.redraw_all(Editor_state)
1197 Editor_state.cursor1 = {line=2, pos=1}
1198 Editor_state.screen_top1 = {line=2, pos=1}
1199 edit.draw(Editor_state)
1200 local y = Editor_state.top
1201 App.screen.check(y, 'def', 'baseline/screen:1')
1202 y = y + Editor_state.line_height
1203 App.screen.check(y, 'ghi', 'baseline/screen:2')
1204 y = y + Editor_state.line_height
1205 App.screen.check(y, 'jkl', 'baseline/screen:3')
1206 -- after hitting the up arrow the screen scrolls up by one line
1207 edit.run_after_keychord(Editor_state, 'up', 'up')
1208 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1209 check_eq(Editor_state.cursor1.line, 1, 'cursor')
1210 y = Editor_state.top
1211 App.screen.check(y, 'abc', 'screen:1')
1212 y = y + Editor_state.line_height
1213 App.screen.check(y, 'def', 'screen:2')
1214 y = y + Editor_state.line_height
1215 App.screen.check(y, 'ghi', 'screen:3')
1218 function test_up_arrow_scrolls_up_by_one_screen_line()
1219 -- display lines starting from second screen line of a line
1220 App.screen.init{width=Editor_state.left+30, height=60}
1221 Editor_state = edit.initialize_test_state()
1222 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1223 Text.redraw_all(Editor_state)
1224 Editor_state.cursor1 = {line=3, pos=6}
1225 Editor_state.screen_top1 = {line=3, pos=5}
1226 edit.draw(Editor_state)
1227 local y = Editor_state.top
1228 App.screen.check(y, 'jkl', 'baseline/screen:1')
1229 y = y + Editor_state.line_height
1230 App.screen.check(y, 'mno', 'baseline/screen:2')
1231 -- after hitting the up arrow the screen scrolls up to first screen line
1232 edit.run_after_keychord(Editor_state, 'up', 'up')
1233 y = Editor_state.top
1234 App.screen.check(y, 'ghi ', 'screen:1')
1235 y = y + Editor_state.line_height
1236 App.screen.check(y, 'jkl', 'screen:2')
1237 y = y + Editor_state.line_height
1238 App.screen.check(y, 'mno', 'screen:3')
1239 check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
1240 check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
1241 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1242 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1245 function test_up_arrow_scrolls_up_to_final_screen_line()
1246 -- display lines starting just after a long line
1247 App.screen.init{width=Editor_state.left+30, height=60}
1248 Editor_state = edit.initialize_test_state()
1249 Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
1250 Text.redraw_all(Editor_state)
1251 Editor_state.cursor1 = {line=2, pos=1}
1252 Editor_state.screen_top1 = {line=2, pos=1}
1253 edit.draw(Editor_state)
1254 local y = Editor_state.top
1255 App.screen.check(y, 'ghi', 'baseline/screen:1')
1256 y = y + Editor_state.line_height
1257 App.screen.check(y, 'jkl', 'baseline/screen:2')
1258 y = y + Editor_state.line_height
1259 App.screen.check(y, 'mno', 'baseline/screen:3')
1260 -- after hitting the up arrow the screen scrolls up to final screen line of previous line
1261 edit.run_after_keychord(Editor_state, 'up', 'up')
1262 y = Editor_state.top
1263 App.screen.check(y, 'def', 'screen:1')
1264 y = y + Editor_state.line_height
1265 App.screen.check(y, 'ghi', 'screen:2')
1266 y = y + Editor_state.line_height
1267 App.screen.check(y, 'jkl', 'screen:3')
1268 check_eq(Editor_state.screen_top1.line, 1, 'screen_top:line')
1269 check_eq(Editor_state.screen_top1.pos, 5, 'screen_top:pos')
1270 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1271 check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
1274 function test_up_arrow_scrolls_up_to_empty_line()
1275 -- display a screenful of text with an empty line just above it outside the screen
1276 App.screen.init{width=120, height=60}
1277 Editor_state = edit.initialize_test_state()
1278 Editor_state.lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}
1279 Text.redraw_all(Editor_state)
1280 Editor_state.cursor1 = {line=2, pos=1}
1281 Editor_state.screen_top1 = {line=2, pos=1}
1282 edit.draw(Editor_state)
1283 local y = Editor_state.top
1284 App.screen.check(y, 'abc', 'baseline/screen:1')
1285 y = y + Editor_state.line_height
1286 App.screen.check(y, 'def', 'baseline/screen:2')
1287 y = y + Editor_state.line_height
1288 App.screen.check(y, 'ghi', 'baseline/screen:3')
1289 -- after hitting the up arrow the screen scrolls up by one line
1290 edit.run_after_keychord(Editor_state, 'up', 'up')
1291 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1292 check_eq(Editor_state.cursor1.line, 1, 'cursor')
1293 y = Editor_state.top
1294 -- empty first line
1295 y = y + Editor_state.line_height
1296 App.screen.check(y, 'abc', 'screen:2')
1297 y = y + Editor_state.line_height
1298 App.screen.check(y, 'def', 'screen:3')
1301 function test_pageup()
1302 App.screen.init{width=120, height=45}
1303 Editor_state = edit.initialize_test_state()
1304 Editor_state.lines = load_array{'abc', 'def', 'ghi'}
1305 Text.redraw_all(Editor_state)
1306 Editor_state.cursor1 = {line=2, pos=1}
1307 Editor_state.screen_top1 = {line=2, pos=1}
1308 -- initially the last two lines are displayed
1309 edit.draw(Editor_state)
1310 local y = Editor_state.top
1311 App.screen.check(y, 'def', 'baseline/screen:1')
1312 y = y + Editor_state.line_height
1313 App.screen.check(y, 'ghi', 'baseline/screen:2')
1314 -- after pageup the cursor goes to first line
1315 edit.run_after_keychord(Editor_state, 'pageup', 'pageup')
1316 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1317 check_eq(Editor_state.cursor1.line, 1, 'cursor')
1318 y = Editor_state.top
1319 App.screen.check(y, 'abc', 'screen:1')
1320 y = y + Editor_state.line_height
1321 App.screen.check(y, 'def', 'screen:2')
1324 function test_pageup_scrolls_up_by_screen_line()
1325 -- display the first three lines with the cursor on the bottom line
1326 App.screen.init{width=Editor_state.left+30, height=60}
1327 Editor_state = edit.initialize_test_state()
1328 Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}
1329 Text.redraw_all(Editor_state)
1330 Editor_state.cursor1 = {line=2, pos=1}
1331 Editor_state.screen_top1 = {line=2, pos=1}
1332 edit.draw(Editor_state)
1333 local y = Editor_state.top
1334 App.screen.check(y, 'ghi', 'baseline/screen:1')
1335 y = y + Editor_state.line_height
1336 App.screen.check(y, 'jkl', 'baseline/screen:2')
1337 y = y + Editor_state.line_height
1338 App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
1339 -- after hitting the page-up key the screen scrolls up to top
1340 edit.run_after_keychord(Editor_state, 'pageup', 'pageup')
1341 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1342 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1343 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1344 y = Editor_state.top
1345 App.screen.check(y, 'abc ', 'screen:1')
1346 y = y + Editor_state.line_height
1347 App.screen.check(y, 'def', 'screen:2')
1348 y = y + Editor_state.line_height
1349 App.screen.check(y, 'ghi', 'screen:3')
1352 function test_pageup_scrolls_up_from_middle_screen_line()
1353 -- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)
1354 App.screen.init{width=Editor_state.left+30, height=60}
1355 Editor_state = edit.initialize_test_state()
1356 Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}
1357 Text.redraw_all(Editor_state)
1358 Editor_state.cursor1 = {line=2, pos=5}
1359 Editor_state.screen_top1 = {line=2, pos=5}
1360 edit.draw(Editor_state)
1361 local y = Editor_state.top
1362 App.screen.check(y, 'jkl', 'baseline/screen:2')
1363 y = y + Editor_state.line_height
1364 App.screen.check(y, 'mno', 'baseline/screen:3') -- line wrapping includes trailing whitespace
1365 -- after hitting the page-up key the screen scrolls up to top
1366 edit.run_after_keychord(Editor_state, 'pageup', 'pageup')
1367 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1368 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1369 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1370 y = Editor_state.top
1371 App.screen.check(y, 'abc ', 'screen:1')
1372 y = y + Editor_state.line_height
1373 App.screen.check(y, 'def', 'screen:2')
1374 y = y + Editor_state.line_height
1375 App.screen.check(y, 'ghi ', 'screen:3')
1378 function test_enter_on_bottom_line_scrolls_down()
1379 -- display a few lines with cursor on bottom line
1380 App.screen.init{width=Editor_state.left+30, height=60}
1381 Editor_state = edit.initialize_test_state()
1382 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1383 Text.redraw_all(Editor_state)
1384 Editor_state.cursor1 = {line=3, pos=2}
1385 Editor_state.screen_top1 = {line=1, pos=1}
1386 edit.draw(Editor_state)
1387 local y = Editor_state.top
1388 App.screen.check(y, 'abc', 'baseline/screen:1')
1389 y = y + Editor_state.line_height
1390 App.screen.check(y, 'def', 'baseline/screen:2')
1391 y = y + Editor_state.line_height
1392 App.screen.check(y, 'ghi', 'baseline/screen:3')
1393 -- after hitting the enter key the screen scrolls down
1394 edit.run_after_keychord(Editor_state, 'return', 'return')
1395 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1396 check_eq(Editor_state.cursor1.line, 4, 'cursor:line')
1397 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1398 y = Editor_state.top
1399 App.screen.check(y, 'def', 'screen:1')
1400 y = y + Editor_state.line_height
1401 App.screen.check(y, 'g', 'screen:2')
1402 y = y + Editor_state.line_height
1403 App.screen.check(y, 'hi', 'screen:3')
1406 function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()
1407 -- display just the bottom line on screen
1408 App.screen.init{width=Editor_state.left+30, height=60}
1409 Editor_state = edit.initialize_test_state()
1410 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1411 Text.redraw_all(Editor_state)
1412 Editor_state.cursor1 = {line=4, pos=2}
1413 Editor_state.screen_top1 = {line=4, pos=1}
1414 edit.draw(Editor_state)
1415 local y = Editor_state.top
1416 App.screen.check(y, 'jkl', 'baseline/screen:1')
1417 -- after hitting the enter key the screen does not scroll down
1418 edit.run_after_keychord(Editor_state, 'return', 'return')
1419 check_eq(Editor_state.screen_top1.line, 4, 'screen_top')
1420 check_eq(Editor_state.cursor1.line, 5, 'cursor:line')
1421 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1422 y = Editor_state.top
1423 App.screen.check(y, 'j', 'screen:1')
1424 y = y + Editor_state.line_height
1425 App.screen.check(y, 'kl', 'screen:2')
1428 function test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()
1429 -- display just an empty bottom line on screen
1430 App.screen.init{width=Editor_state.left+30, height=60}
1431 Editor_state = edit.initialize_test_state()
1432 Editor_state.lines = load_array{'abc', ''}
1433 Text.redraw_all(Editor_state)
1434 Editor_state.cursor1 = {line=2, pos=1}
1435 Editor_state.screen_top1 = {line=2, pos=1}
1436 edit.draw(Editor_state)
1437 -- after hitting the inserting_text key the screen does not scroll down
1438 edit.run_after_text_input(Editor_state, 'a')
1439 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1440 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
1441 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
1442 local y = Editor_state.top
1443 App.screen.check(y, 'a', 'screen:1')
1446 function test_typing_on_bottom_line_scrolls_down()
1447 -- display a few lines with cursor on bottom line
1448 App.screen.init{width=Editor_state.left+30, height=60}
1449 Editor_state = edit.initialize_test_state()
1450 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1451 Text.redraw_all(Editor_state)
1452 Editor_state.cursor1 = {line=3, pos=4}
1453 Editor_state.screen_top1 = {line=1, pos=1}
1454 edit.draw(Editor_state)
1455 local y = Editor_state.top
1456 App.screen.check(y, 'abc', 'baseline/screen:1')
1457 y = y + Editor_state.line_height
1458 App.screen.check(y, 'def', 'baseline/screen:2')
1459 y = y + Editor_state.line_height
1460 App.screen.check(y, 'ghi', 'baseline/screen:3')
1461 -- after typing something the line wraps and the screen scrolls down
1462 edit.run_after_text_input(Editor_state, 'j')
1463 edit.run_after_text_input(Editor_state, 'k')
1464 edit.run_after_text_input(Editor_state, 'l')
1465 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1466 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1467 check_eq(Editor_state.cursor1.pos, 7, 'cursor:pos')
1468 y = Editor_state.top
1469 App.screen.check(y, 'def', 'screen:1')
1470 y = y + Editor_state.line_height
1471 App.screen.check(y, 'ghij', 'screen:2')
1472 y = y + Editor_state.line_height
1473 App.screen.check(y, 'kl', 'screen:3')
1476 function test_left_arrow_scrolls_up_in_wrapped_line()
1477 -- display lines starting from second screen line of a line
1478 App.screen.init{width=Editor_state.left+30, height=60}
1479 Editor_state = edit.initialize_test_state()
1480 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1481 Text.redraw_all(Editor_state)
1482 Editor_state.screen_top1 = {line=3, pos=5}
1483 -- cursor is at top of screen
1484 Editor_state.cursor1 = {line=3, pos=5}
1485 edit.draw(Editor_state)
1486 local y = Editor_state.top
1487 App.screen.check(y, 'jkl', 'baseline/screen:1')
1488 y = y + Editor_state.line_height
1489 App.screen.check(y, 'mno', 'baseline/screen:2')
1490 -- after hitting the left arrow the screen scrolls up to first screen line
1491 edit.run_after_keychord(Editor_state, 'left', 'left')
1492 y = Editor_state.top
1493 App.screen.check(y, 'ghi ', 'screen:1')
1494 y = y + Editor_state.line_height
1495 App.screen.check(y, 'jkl', 'screen:2')
1496 y = y + Editor_state.line_height
1497 App.screen.check(y, 'mno', 'screen:3')
1498 check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
1499 check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
1500 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1501 check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
1504 function test_right_arrow_scrolls_down_in_wrapped_line()
1505 -- display the first three lines with the cursor on the bottom line
1506 App.screen.init{width=Editor_state.left+30, height=60}
1507 Editor_state = edit.initialize_test_state()
1508 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1509 Text.redraw_all(Editor_state)
1510 Editor_state.screen_top1 = {line=1, pos=1}
1511 -- cursor is at bottom right of screen
1512 Editor_state.cursor1 = {line=3, pos=5}
1513 edit.draw(Editor_state)
1514 local y = Editor_state.top
1515 App.screen.check(y, 'abc', 'baseline/screen:1')
1516 y = y + Editor_state.line_height
1517 App.screen.check(y, 'def', 'baseline/screen:2')
1518 y = y + Editor_state.line_height
1519 App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
1520 -- after hitting the right arrow the screen scrolls down by one line
1521 edit.run_after_keychord(Editor_state, 'right', 'right')
1522 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1523 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1524 check_eq(Editor_state.cursor1.pos, 6, 'cursor:pos')
1525 y = Editor_state.top
1526 App.screen.check(y, 'def', 'screen:1')
1527 y = y + Editor_state.line_height
1528 App.screen.check(y, 'ghi ', 'screen:2')
1529 y = y + Editor_state.line_height
1530 App.screen.check(y, 'jkl', 'screen:3')
1533 function test_home_scrolls_up_in_wrapped_line()
1534 -- display lines starting from second screen line of a line
1535 App.screen.init{width=Editor_state.left+30, height=60}
1536 Editor_state = edit.initialize_test_state()
1537 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1538 Text.redraw_all(Editor_state)
1539 Editor_state.screen_top1 = {line=3, pos=5}
1540 -- cursor is at top of screen
1541 Editor_state.cursor1 = {line=3, pos=5}
1542 edit.draw(Editor_state)
1543 local y = Editor_state.top
1544 App.screen.check(y, 'jkl', 'baseline/screen:1')
1545 y = y + Editor_state.line_height
1546 App.screen.check(y, 'mno', 'baseline/screen:2')
1547 -- after hitting home the screen scrolls up to first screen line
1548 edit.run_after_keychord(Editor_state, 'home', 'home')
1549 y = Editor_state.top
1550 App.screen.check(y, 'ghi ', 'screen:1')
1551 y = y + Editor_state.line_height
1552 App.screen.check(y, 'jkl', 'screen:2')
1553 y = y + Editor_state.line_height
1554 App.screen.check(y, 'mno', 'screen:3')
1555 check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
1556 check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
1557 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1558 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1561 function test_end_scrolls_down_in_wrapped_line()
1562 -- display the first three lines with the cursor on the bottom line
1563 App.screen.init{width=Editor_state.left+30, height=60}
1564 Editor_state = edit.initialize_test_state()
1565 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1566 Text.redraw_all(Editor_state)
1567 Editor_state.screen_top1 = {line=1, pos=1}
1568 -- cursor is at bottom right of screen
1569 Editor_state.cursor1 = {line=3, pos=5}
1570 edit.draw(Editor_state)
1571 local y = Editor_state.top
1572 App.screen.check(y, 'abc', 'baseline/screen:1')
1573 y = y + Editor_state.line_height
1574 App.screen.check(y, 'def', 'baseline/screen:2')
1575 y = y + Editor_state.line_height
1576 App.screen.check(y, 'ghi ', 'baseline/screen:3') -- line wrapping includes trailing whitespace
1577 -- after hitting end the screen scrolls down by one line
1578 edit.run_after_keychord(Editor_state, 'end', 'end')
1579 check_eq(Editor_state.screen_top1.line, 2, 'screen_top')
1580 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1581 check_eq(Editor_state.cursor1.pos, 8, 'cursor:pos')
1582 y = Editor_state.top
1583 App.screen.check(y, 'def', 'screen:1')
1584 y = y + Editor_state.line_height
1585 App.screen.check(y, 'ghi ', 'screen:2')
1586 y = y + Editor_state.line_height
1587 App.screen.check(y, 'jkl', 'screen:3')
1590 function test_position_cursor_on_recently_edited_wrapping_line()
1591 -- draw a line wrapping over 2 screen lines
1592 App.screen.init{width=100, height=200}
1593 Editor_state = edit.initialize_test_state()
1594 Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}
1595 Text.redraw_all(Editor_state)
1596 Editor_state.cursor1 = {line=1, pos=25}
1597 Editor_state.screen_top1 = {line=1, pos=1}
1598 edit.draw(Editor_state)
1599 local y = Editor_state.top
1600 App.screen.check(y, 'abc def ghi ', 'baseline1/screen:1')
1601 y = y + Editor_state.line_height
1602 App.screen.check(y, 'jkl mno pqr ', 'baseline1/screen:2')
1603 y = y + Editor_state.line_height
1604 App.screen.check(y, 'xyz', 'baseline1/screen:3')
1605 -- add to the line until it's wrapping over 3 screen lines
1606 edit.run_after_text_input(Editor_state, 's')
1607 edit.run_after_text_input(Editor_state, 't')
1608 edit.run_after_text_input(Editor_state, 'u')
1609 check_eq(Editor_state.cursor1.pos, 28, 'cursor:pos')
1610 y = Editor_state.top
1611 App.screen.check(y, 'abc def ghi ', 'baseline2/screen:1')
1612 y = y + Editor_state.line_height
1613 App.screen.check(y, 'jkl mno pqr ', 'baseline2/screen:2')
1614 y = y + Editor_state.line_height
1615 App.screen.check(y, 'stu', 'baseline2/screen:3')
1616 -- try to move the cursor earlier in the third screen line by clicking the mouse
1617 edit.run_after_mouse_release(Editor_state, Editor_state.left+2,Editor_state.top+Editor_state.line_height*2+5, 1)
1618 -- cursor should move
1619 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1620 check_eq(Editor_state.cursor1.pos, 25, 'cursor:pos')
1623 function test_backspace_can_scroll_up()
1624 -- display the lines 2/3/4 with the cursor on line 2
1625 App.screen.init{width=120, height=60}
1626 Editor_state = edit.initialize_test_state()
1627 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}
1628 Text.redraw_all(Editor_state)
1629 Editor_state.cursor1 = {line=2, pos=1}
1630 Editor_state.screen_top1 = {line=2, pos=1}
1631 edit.draw(Editor_state)
1632 local y = Editor_state.top
1633 App.screen.check(y, 'def', 'baseline/screen:1')
1634 y = y + Editor_state.line_height
1635 App.screen.check(y, 'ghi', 'baseline/screen:2')
1636 y = y + Editor_state.line_height
1637 App.screen.check(y, 'jkl', 'baseline/screen:3')
1638 -- after hitting backspace the screen scrolls up by one line
1639 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1640 check_eq(Editor_state.screen_top1.line, 1, 'screen_top')
1641 check_eq(Editor_state.cursor1.line, 1, 'cursor')
1642 y = Editor_state.top
1643 App.screen.check(y, 'abcdef', 'screen:1')
1644 y = y + Editor_state.line_height
1645 App.screen.check(y, 'ghi', 'screen:2')
1646 y = y + Editor_state.line_height
1647 App.screen.check(y, 'jkl', 'screen:3')
1650 function test_backspace_can_scroll_up_screen_line()
1651 -- display lines starting from second screen line of a line
1652 App.screen.init{width=Editor_state.left+30, height=60}
1653 Editor_state = edit.initialize_test_state()
1654 Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}
1655 Text.redraw_all(Editor_state)
1656 Editor_state.cursor1 = {line=3, pos=5}
1657 Editor_state.screen_top1 = {line=3, pos=5}
1658 edit.draw(Editor_state)
1659 local y = Editor_state.top
1660 App.screen.check(y, 'jkl', 'baseline/screen:1')
1661 y = y + Editor_state.line_height
1662 App.screen.check(y, 'mno', 'baseline/screen:2')
1663 -- after hitting backspace the screen scrolls up by one screen line
1664 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1665 y = Editor_state.top
1666 App.screen.check(y, 'ghij', 'screen:1')
1667 y = y + Editor_state.line_height
1668 App.screen.check(y, 'kl', 'screen:2')
1669 y = y + Editor_state.line_height
1670 App.screen.check(y, 'mno', 'screen:3')
1671 check_eq(Editor_state.screen_top1.line, 3, 'screen_top:line')
1672 check_eq(Editor_state.screen_top1.pos, 1, 'screen_top:pos')
1673 check_eq(Editor_state.cursor1.line, 3, 'cursor:line')
1674 check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
1677 function test_backspace_past_line_boundary()
1678 -- position cursor at start of a (non-first) line
1679 App.screen.init{width=Editor_state.left+30, height=60}
1680 Editor_state = edit.initialize_test_state()
1681 Editor_state.lines = load_array{'abc', 'def'}
1682 Text.redraw_all(Editor_state)
1683 Editor_state.cursor1 = {line=2, pos=1}
1684 -- backspace joins with previous line
1685 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1686 check_eq(Editor_state.lines[1].data, 'abcdef', 'check')
1689 -- some tests for operating over selections created using Shift- chords
1690 -- we're just testing delete_selection, and it works the same for all keys
1692 function test_backspace_over_selection()
1693 -- select just one character within a line with cursor before selection
1694 App.screen.init{width=Editor_state.left+30, height=60}
1695 Editor_state = edit.initialize_test_state()
1696 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
1697 Text.redraw_all(Editor_state)
1698 Editor_state.cursor1 = {line=1, pos=1}
1699 Editor_state.selection1 = {line=1, pos=2}
1700 -- backspace deletes the selected character, even though it's after the cursor
1701 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1702 check_eq(Editor_state.lines[1].data, 'bc', 'data')
1703 -- cursor (remains) at start of selection
1704 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1705 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1706 -- selection is cleared
1707 check_nil(Editor_state.selection1.line, 'selection')
1710 function test_backspace_over_selection_reverse()
1711 -- select just one character within a line with cursor after selection
1712 App.screen.init{width=Editor_state.left+30, height=60}
1713 Editor_state = edit.initialize_test_state()
1714 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
1715 Text.redraw_all(Editor_state)
1716 Editor_state.cursor1 = {line=1, pos=2}
1717 Editor_state.selection1 = {line=1, pos=1}
1718 -- backspace deletes the selected character
1719 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1720 check_eq(Editor_state.lines[1].data, 'bc', 'data')
1721 -- cursor moves to start of selection
1722 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1723 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1724 -- selection is cleared
1725 check_nil(Editor_state.selection1.line, 'selection')
1728 function test_backspace_over_multiple_lines()
1729 -- select just one character within a line with cursor after selection
1730 App.screen.init{width=Editor_state.left+30, height=60}
1731 Editor_state = edit.initialize_test_state()
1732 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
1733 Text.redraw_all(Editor_state)
1734 Editor_state.cursor1 = {line=1, pos=2}
1735 Editor_state.selection1 = {line=4, pos=2}
1736 -- backspace deletes the region and joins the remaining portions of lines on either side
1737 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1738 check_eq(Editor_state.lines[1].data, 'akl', 'data:1')
1739 check_eq(Editor_state.lines[2].data, 'mno', 'data:2')
1740 -- cursor remains at start of selection
1741 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1742 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
1743 -- selection is cleared
1744 check_nil(Editor_state.selection1.line, 'selection')
1747 function test_backspace_to_end_of_line()
1748 -- select region from cursor to end of line
1749 App.screen.init{width=Editor_state.left+30, height=60}
1750 Editor_state = edit.initialize_test_state()
1751 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
1752 Text.redraw_all(Editor_state)
1753 Editor_state.cursor1 = {line=1, pos=2}
1754 Editor_state.selection1 = {line=1, pos=4}
1755 -- backspace deletes rest of line without joining to any other line
1756 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1757 check_eq(Editor_state.lines[1].data, 'a', 'data:1')
1758 check_eq(Editor_state.lines[2].data, 'def', 'data:2')
1759 -- cursor remains at start of selection
1760 check_eq(Editor_state.cursor1.line, 1, 'cursor:line')
1761 check_eq(Editor_state.cursor1.pos, 2, 'cursor:pos')
1762 -- selection is cleared
1763 check_nil(Editor_state.selection1.line, 'selection')
1766 function test_backspace_to_start_of_line()
1767 -- select region from cursor to start of line
1768 App.screen.init{width=Editor_state.left+30, height=60}
1769 Editor_state = edit.initialize_test_state()
1770 Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl', 'mno'}
1771 Text.redraw_all(Editor_state)
1772 Editor_state.cursor1 = {line=2, pos=1}
1773 Editor_state.selection1 = {line=2, pos=3}
1774 -- backspace deletes beginning of line without joining to any other line
1775 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1776 check_eq(Editor_state.lines[1].data, 'abc', 'data:1')
1777 check_eq(Editor_state.lines[2].data, 'f', 'data:2')
1778 -- cursor remains at start of selection
1779 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
1780 check_eq(Editor_state.cursor1.pos, 1, 'cursor:pos')
1781 -- selection is cleared
1782 check_nil(Editor_state.selection1.line, 'selection')
1785 function test_undo_insert_text()
1786 App.screen.init{width=120, height=60}
1787 Editor_state = edit.initialize_test_state()
1788 Editor_state.lines = load_array{'abc', 'def', 'xyz'}
1789 Text.redraw_all(Editor_state)
1790 Editor_state.cursor1 = {line=2, pos=4}
1791 Editor_state.screen_top1 = {line=1, pos=1}
1792 -- insert a character
1793 edit.draw(Editor_state)
1794 edit.run_after_text_input(Editor_state, 'g')
1795 check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
1796 check_eq(Editor_state.cursor1.pos, 5, 'baseline/cursor:pos')
1797 check_nil(Editor_state.selection1.line, 'baseline/selection:line')
1798 check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')
1799 local y = Editor_state.top
1800 App.screen.check(y, 'abc', 'baseline/screen:1')
1801 y = y + Editor_state.line_height
1802 App.screen.check(y, 'defg', 'baseline/screen:2')
1803 y = y + Editor_state.line_height
1804 App.screen.check(y, 'xyz', 'baseline/screen:3')
1805 -- undo
1806 edit.run_after_keychord(Editor_state, 'C-z', 'z')
1807 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
1808 check_eq(Editor_state.cursor1.pos, 4, 'cursor:pos')
1809 check_nil(Editor_state.selection1.line, 'selection:line')
1810 check_nil(Editor_state.selection1.pos, 'selection:pos')
1811 y = Editor_state.top
1812 App.screen.check(y, 'abc', 'screen:1')
1813 y = y + Editor_state.line_height
1814 App.screen.check(y, 'def', 'screen:2')
1815 y = y + Editor_state.line_height
1816 App.screen.check(y, 'xyz', 'screen:3')
1819 function test_undo_delete_text()
1820 App.screen.init{width=120, height=60}
1821 Editor_state = edit.initialize_test_state()
1822 Editor_state.lines = load_array{'abc', 'defg', 'xyz'}
1823 Text.redraw_all(Editor_state)
1824 Editor_state.cursor1 = {line=2, pos=5}
1825 Editor_state.screen_top1 = {line=1, pos=1}
1826 -- delete a character
1827 edit.run_after_keychord(Editor_state, 'backspace', 'backspace')
1828 check_eq(Editor_state.cursor1.line, 2, 'baseline/cursor:line')
1829 check_eq(Editor_state.cursor1.pos, 4, 'baseline/cursor:pos')
1830 check_nil(Editor_state.selection1.line, 'baseline/selection:line')
1831 check_nil(Editor_state.selection1.pos, 'baseline/selection:pos')
1832 local y = Editor_state.top
1833 App.screen.check(y, 'abc', 'baseline/screen:1')
1834 y = y + Editor_state.line_height
1835 App.screen.check(y, 'def', 'baseline/screen:2')
1836 y = y + Editor_state.line_height
1837 App.screen.check(y, 'xyz', 'baseline/screen:3')
1838 -- undo
1839 --? -- after undo, the backspaced key is selected
1840 edit.run_after_keychord(Editor_state, 'C-z', 'z')
1841 check_eq(Editor_state.cursor1.line, 2, 'cursor:line')
1842 check_eq(Editor_state.cursor1.pos, 5, 'cursor:pos')
1843 check_nil(Editor_state.selection1.line, 'selection:line')
1844 check_nil(Editor_state.selection1.pos, 'selection:pos')
1845 --? check_eq(Editor_state.selection1.line, 2, 'selection:line')
1846 --? check_eq(Editor_state.selection1.pos, 4, 'selection:pos')
1847 y = Editor_state.top
1848 App.screen.check(y, 'abc', 'screen:1')
1849 y = y + Editor_state.line_height
1850 App.screen.check(y, 'defg', 'screen:2')
1851 y = y + Editor_state.line_height
1852 App.screen.check(y, 'xyz', 'screen:3')
1855 function test_undo_restores_selection()
1856 -- display a line of text with some part selected
1857 App.screen.init{width=75, height=80}
1858 Editor_state = edit.initialize_test_state()
1859 Editor_state.lines = load_array{'abc'}
1860 Text.redraw_all(Editor_state)
1861 Editor_state.cursor1 = {line=1, pos=1}
1862 Editor_state.selection1 = {line=1, pos=2}
1863 Editor_state.screen_top1 = {line=1, pos=1}
1864 edit.draw(Editor_state)
1865 -- delete selected text
1866 edit.run_after_text_input(Editor_state, 'x')
1867 check_eq(Editor_state.lines[1].data, 'xbc', 'baseline')
1868 check_nil(Editor_state.selection1.line, 'baseline:selection')
1869 -- undo
1870 edit.run_after_keychord(Editor_state, 'C-z', 'z')
1871 edit.run_after_keychord(Editor_state, 'C-z', 'z')
1872 -- selection is restored
1873 check_eq(Editor_state.selection1.line, 1, 'line')
1874 check_eq(Editor_state.selection1.pos, 2, 'pos')
1877 function test_search()
1878 App.screen.init{width=120, height=60}
1879 Editor_state = edit.initialize_test_state()
1880 Editor_state.lines = load_array{'abc', 'def', 'ghi', '’deg'} -- contains unicode quote in final line
1881 Text.redraw_all(Editor_state)
1882 Editor_state.cursor1 = {line=1, pos=1}
1883 Editor_state.screen_top1 = {line=1, pos=1}
1884 edit.draw(Editor_state)
1885 -- search for a string
1886 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1887 edit.run_after_text_input(Editor_state, 'd')
1888 edit.run_after_keychord(Editor_state, 'return', 'return')
1889 check_eq(Editor_state.cursor1.line, 2, '1/cursor:line')
1890 check_eq(Editor_state.cursor1.pos, 1, '1/cursor:pos')
1891 -- reset cursor
1892 Editor_state.cursor1 = {line=1, pos=1}
1893 Editor_state.screen_top1 = {line=1, pos=1}
1894 -- search for second occurrence
1895 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1896 edit.run_after_text_input(Editor_state, 'de')
1897 edit.run_after_keychord(Editor_state, 'down', 'down')
1898 edit.run_after_keychord(Editor_state, 'return', 'return')
1899 check_eq(Editor_state.cursor1.line, 4, '2/cursor:line')
1900 check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')
1903 function test_search_upwards()
1904 App.screen.init{width=120, height=60}
1905 Editor_state = edit.initialize_test_state()
1906 Editor_state.lines = load_array{'’abc', 'abd'} -- contains unicode quote
1907 Text.redraw_all(Editor_state)
1908 Editor_state.cursor1 = {line=2, pos=1}
1909 Editor_state.screen_top1 = {line=1, pos=1}
1910 edit.draw(Editor_state)
1911 -- search for a string
1912 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1913 edit.run_after_text_input(Editor_state, 'a')
1914 -- search for previous occurrence
1915 edit.run_after_keychord(Editor_state, 'up', 'up')
1916 check_eq(Editor_state.cursor1.line, 1, '2/cursor:line')
1917 check_eq(Editor_state.cursor1.pos, 2, '2/cursor:pos')
1920 function test_search_wrap()
1921 App.screen.init{width=120, height=60}
1922 Editor_state = edit.initialize_test_state()
1923 Editor_state.lines = load_array{'’abc', 'def'} -- contains unicode quote in first line
1924 Text.redraw_all(Editor_state)
1925 Editor_state.cursor1 = {line=2, pos=1}
1926 Editor_state.screen_top1 = {line=1, pos=1}
1927 edit.draw(Editor_state)
1928 -- search for a string
1929 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1930 edit.run_after_text_input(Editor_state, 'a')
1931 edit.run_after_keychord(Editor_state, 'return', 'return')
1932 -- cursor wraps
1933 check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
1934 check_eq(Editor_state.cursor1.pos, 2, '1/cursor:pos')
1937 function test_search_wrap_upwards()
1938 App.screen.init{width=120, height=60}
1939 Editor_state = edit.initialize_test_state()
1940 Editor_state.lines = load_array{'abc ’abd'} -- contains unicode quote
1941 Text.redraw_all(Editor_state)
1942 Editor_state.cursor1 = {line=1, pos=1}
1943 Editor_state.screen_top1 = {line=1, pos=1}
1944 edit.draw(Editor_state)
1945 -- search upwards for a string
1946 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1947 edit.run_after_text_input(Editor_state, 'a')
1948 edit.run_after_keychord(Editor_state, 'up', 'up')
1949 -- cursor wraps
1950 check_eq(Editor_state.cursor1.line, 1, '1/cursor:line')
1951 check_eq(Editor_state.cursor1.pos, 6, '1/cursor:pos')
1954 function test_search_downwards_from_end_of_line()
1955 App.screen.init{width=120, height=60}
1956 Editor_state = edit.initialize_test_state()
1957 Editor_state.lines = load_array{'abc', 'def', 'ghi'}
1958 Text.redraw_all(Editor_state)
1959 Editor_state.cursor1 = {line=1, pos=4}
1960 Editor_state.screen_top1 = {line=1, pos=1}
1961 edit.draw(Editor_state)
1962 -- search for empty string
1963 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1964 edit.run_after_keychord(Editor_state, 'down', 'down')
1965 -- no crash
1968 function test_search_downwards_from_final_pos_of_line()
1969 App.screen.init{width=120, height=60}
1970 Editor_state = edit.initialize_test_state()
1971 Editor_state.lines = load_array{'abc', 'def', 'ghi'}
1972 Text.redraw_all(Editor_state)
1973 Editor_state.cursor1 = {line=1, pos=3}
1974 Editor_state.screen_top1 = {line=1, pos=1}
1975 edit.draw(Editor_state)
1976 -- search for empty string
1977 edit.run_after_keychord(Editor_state, 'C-f', 'f')
1978 edit.run_after_keychord(Editor_state, 'down', 'down')
1979 -- no crash