source_text_tests.lua 86 KB

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