text_tests.lua 80 KB

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