text_tests.lua 79 KB

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