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