C3GUE45IQNZNGKQVSINNGVVFEBWVWDT6Z4WVOEV5SQLGFUNPZXUQC 6BGU2SBJIVRJVTJERJVIIBWFYLWN7WSIHS34YIT3KCIWZYNUZ7ZQC THRPA4VVHXXPUP56OBKHCBMVMMU7MSSVEZXB5YLY6AS5PUBOVWJQC ORKN6EOBUFVAD2TXYW5OIKSL55RU24LOFDTTTXHDZUZ57QRDCY7QC R5QXEHUIZLELJGGCZAE7ATNS3CLRJ7JFRENMGH4XXH24C5WABZDQC BLWAYPKV3MLDZ4ALXLUJ25AIR6PCIL4RFYNRYLB26GFVC2KQBYBAC 2CK5QI7WA7M4IVSACFGOJYAIDKRUTZVMMPSFWEJTUNMWTN7AX4NAC VHQCNMARPMNBSIUFLJG7HVK4QGDNPCGNVFLHS3I4IGNVSV5MRLYQC DRFE3B3ZKRG4RY2R5Q3SDFD3LH4EXUX3CZCDFBNAXVI2SLDS57PAC LYN3L74WRXZI4KNNIMNLPRFQ36RAGPWNE2O5AMB42H3CSTI6QM6QC QXVD2RIFQPTO3H6J3IJHRHRDRBW6C6MLUWMCEDQ6Z75SW2TMRBIQC DLQAEAC76KLM3KZXQ2C5DASP4IBS64GR6L7QYEP67CNXJ6LRL7LQC JIK7ZRYIWGJRXEHVI2O3HF2P7IZGJTWE6E2J5YHQQTMI72MFXNTAC KYNGDE2CKNOKUC2XMAS5MEU6YT2C3IW5SIZLOJE64G3ERT7BSWFAC HYEAFRZ2UEKDYTAE2GDQLHEJBPQASP2NDLMXB7F6MTVK2BKOXKEAC 3OTESDW65UJ2W5RIXA6FNKRSD7TBCZTCCCEAYOQMEVGYZ6RCU34QC K2X6G75Z6XBC4DVIRWC5HC7XA3A2SKOM3MWSQTCFEYWIJL7LME2QC VP5KC4XZBKD536KCBFO47UKH74RNONENDCFJAHUMVUTPVPGJWD4AC APX2PY6GAMJSUH7SFSMBFOQJBSAWLLOCKH4L4ZQP2VLHNEXJPREAC JOPVPUSAMMU6RFVDQR4NJC4GNNUFB7GPKVH7OS5FKCYS5QZ53VLQC 36Z442IVPXHZ7D2QI26YLN3TDDEMDRQ2GKBYQAD6NUHQZVCCY4VAC D4B52CQ2QKG2HQKFUQOO5S2ME325DTW3PH2D7SBXCW4BPQFYG7CAC 3PSFWAILGRA4OYXWS2DX7VF332AIBPYBXHEA4GIQY2XEJVD65UMAC XRP727K3D2OPH2CJHBYQWLMK2QHQB4QTA4KP34JLTM7ETOLBCC3QC VBU5YHLRO5ZSKFWBJRX7DWQGWPEHEWZMRRVV2WMWDJ54PKUNYCNQC ATQO62TFDZ7J4RCOSB3K2QCCB5R6PNYQIIGNXTLZMEFG5UG5PUJQC SHUFMMIV7AQ5ZKNVZX6D7VQN3LC6S7ZSYOF4T3F6Z5AG62MHCSXQC KKMFQDR43ZWVCDRHQLWWX3FCWCFA3ZSXYOBRJNPHUQZR2XPKWULAC OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC TXDMRA5JEAML2GF5QY4ATU22G3NI7DQWPGO4U5OZNP7NGK4JT6WQC JCSLDGAH2F6AIY4Z6XM6K4LOMW7EFY3E4NF5YXLMHLTYTX3A4Z3QC 2Y7YH7UPQWDNYDJN4BYY2MOHA36B2BIRX6DMIAKHJPQC7UP2R6NQC function Drawing.keychord_press(State, chord)if chord == 'C-p' and not App.mouse_down(1) thenState.current_drawing_mode = 'freehand'elseif App.mouse_down(1) and chord == 'l' thenState.current_drawing_mode = 'line'local _,drawing = Drawing.current_drawing(State)if drawing.pending.mode == 'freehand' thendrawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' thendrawing.pending.p1 = drawing.pending.vertices[1]elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' thendrawing.pending.p1 = drawing.pending.centerenddrawing.pending.mode = 'line'elseif chord == 'C-l' and not App.mouse_down(1) thenState.current_drawing_mode = 'line'elseif App.mouse_down(1) and chord == 'm' thenState.current_drawing_mode = 'manhattan'local drawing = Drawing.select_drawing_at_mouse(State)if drawing.pending.mode == 'freehand' thendrawing.pending.p1 = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)elseif drawing.pending.mode == 'line' then-- do nothingelseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' thendrawing.pending.p1 = drawing.pending.vertices[1]elseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' thendrawing.pending.p1 = drawing.pending.centerenddrawing.pending.mode = 'manhattan'elseif chord == 'C-m' and not App.mouse_down(1) thenState.current_drawing_mode = 'manhattan'elseif chord == 'C-g' and not App.mouse_down(1) thenState.current_drawing_mode = 'polygon'elseif App.mouse_down(1) and chord == 'g' thenState.current_drawing_mode = 'polygon'local _,drawing = Drawing.current_drawing(State)if drawing.pending.mode == 'freehand' thendrawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' thenif drawing.pending.vertices == nil thendrawing.pending.vertices = {drawing.pending.p1}endelseif drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' then-- reuse existing verticeselseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' thendrawing.pending.vertices = {drawing.pending.center}enddrawing.pending.mode = 'polygon'elseif chord == 'C-r' and not App.mouse_down(1) thenState.current_drawing_mode = 'rectangle'elseif App.mouse_down(1) and chord == 'r' thenState.current_drawing_mode = 'rectangle'local _,drawing = Drawing.current_drawing(State)if drawing.pending.mode == 'freehand' thendrawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' thenif drawing.pending.vertices == nil thendrawing.pending.vertices = {drawing.pending.p1}endelseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'square' then-- reuse existing (1-2) verticeselseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' thendrawing.pending.vertices = {drawing.pending.center}enddrawing.pending.mode = 'rectangle'elseif chord == 'C-s' and not App.mouse_down(1) thenState.current_drawing_mode = 'square'elseif App.mouse_down(1) and chord == 's' thenState.current_drawing_mode = 'square'local _,drawing = Drawing.current_drawing(State)if drawing.pending.mode == 'freehand' thendrawing.pending.vertices = {Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)}elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' thenif drawing.pending.vertices == nil thendrawing.pending.vertices = {drawing.pending.p1}endelseif drawing.pending.mode == 'polygon' thenwhile #drawing.pending.vertices > 2 dotable.remove(drawing.pending.vertices)endelseif drawing.pending.mode == 'rectangle' then-- reuse existing (1-2) verticeselseif drawing.pending.mode == 'circle' or drawing.pending.mode == 'arc' thendrawing.pending.vertices = {drawing.pending.center}enddrawing.pending.mode = 'square'elseif App.mouse_down(1) and chord == 'p' and State.current_drawing_mode == 'polygon' thenlocal _,drawing,line_cache = Drawing.current_drawing(State)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)table.insert(drawing.pending.vertices, j)elseif App.mouse_down(1) and chord == 'p' and (State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square') thenlocal _,drawing,line_cache = Drawing.current_drawing(State)local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)local j = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)while #drawing.pending.vertices >= 2 dotable.remove(drawing.pending.vertices)endtable.insert(drawing.pending.vertices, j)elseif chord == 'C-o' and not App.mouse_down(1) thenState.current_drawing_mode = 'circle'elseif App.mouse_down(1) and chord == 'a' and State.current_drawing_mode == 'circle' thenlocal _,drawing,line_cache = Drawing.current_drawing(State)drawing.pending.mode = 'arc'local mx,my = Drawing.coord(App.mouse_x()-State.left, State.width), Drawing.coord(App.mouse_y()-line_cache.starty, State.width)local center = drawing.points[drawing.pending.center]drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))drawing.pending.start_angle = geom.angle(center.x,center.y, mx,my)elseif App.mouse_down(1) and chord == 'o' thenState.current_drawing_mode = 'circle'local _,drawing = Drawing.current_drawing(State)if drawing.pending.mode == 'freehand' thendrawing.pending.center = Drawing.find_or_insert_point(drawing.points, drawing.pending.points[1].x, drawing.pending.points[1].y, State.width)elseif drawing.pending.mode == 'line' or drawing.pending.mode == 'manhattan' thendrawing.pending.center = drawing.pending.p1elseif drawing.pending.mode == 'polygon' or drawing.pending.mode == 'rectangle' or drawing.pending.mode == 'square' thendrawing.pending.center = drawing.pending.vertices[1]enddrawing.pending.mode = 'circle'elseif chord == 'C-u' and not App.mouse_down(1) thenlocal drawing_index,drawing,line_cache,i,p = Drawing.select_point_at_mouse(State)if drawing thenif State.previous_drawing_mode == nil thenState.previous_drawing_mode = State.current_drawing_modeendState.current_drawing_mode = 'move'drawing.pending = {mode=State.current_drawing_mode, target_point=p, target_point_index=i}State.lines.current_drawing_index = drawing_indexState.lines.current_drawing = drawingendelseif chord == 'C-n' and not App.mouse_down(1) thenlocal drawing_index,drawing,line_cache,point_index,p = Drawing.select_point_at_mouse(State)if drawing thenif State.previous_drawing_mode == nil then-- don't clobberState.previous_drawing_mode = State.current_drawing_modeendState.current_drawing_mode = 'name'p.name = ''drawing.pending = {mode=State.current_drawing_mode, target_point=point_index}State.lines.current_drawing_index = drawing_indexState.lines.current_drawing = drawingendelseif chord == 'C-d' and not App.mouse_down(1) thenlocal _,drawing,_,i,p = Drawing.select_point_at_mouse(State)if drawing thenfor _,shape in ipairs(drawing.shapes) doif Drawing.contains_point(shape, i) thenif shape.mode == 'polygon' thenlocal idx = table.find(shape.vertices, i)assert(idx)table.remove(shape.vertices, idx)if #shape.vertices < 3 thenshape.mode = 'deleted'endelseshape.mode = 'deleted'endendenddrawing.points[i].deleted = trueendlocal drawing,_,_,shape = Drawing.select_shape_at_mouse(State)if drawing thenshape.mode = 'deleted'endelseif chord == 'C-h' and not App.mouse_down(1) thenlocal drawing = Drawing.select_drawing_at_mouse(State)if drawing thendrawing.show_help = trueendelseif chord == 'escape' and App.mouse_down(1) thenlocal _,drawing = Drawing.current_drawing(State)drawing.pending = {}endendfunction Drawing.complete_rectangle(firstx,firsty, secondx,secondy, x,y)if firstx == secondx thenreturn x,secondy, x,firstyendif firsty == secondy thenreturn secondx,y, firstx,yendlocal first_slope = (secondy-firsty)/(secondx-firstx)-- slope of second edge:-- -1/first_slope-- equation of line containing the second edge:-- y-secondy = -1/first_slope*(x-secondx)-- => 1/first_slope*x + y + (- secondy - secondx/first_slope) = 0-- now we want to find the point on this line that's closest to the mouse pointer.-- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Line_defined_by_an_equationlocal a = 1/first_slopelocal c = -secondy - secondx/first_slopelocal thirdx = round(((x-a*y) - a*c) / (a*a + 1))local thirdy = round((a*(-x + a*y) - c) / (a*a + 1))-- slope of third edge = first_slope-- equation of line containing third edge:-- y - thirdy = first_slope*(x-thirdx)-- => -first_slope*x + y + (-thirdy + thirdx*first_slope) = 0-- now we want to find the point on this line that's closest to the first pointlocal a = -first_slopelocal c = -thirdy + thirdx*first_slopelocal fourthx = round(((firstx-a*firsty) - a*c) / (a*a + 1))local fourthy = round((a*(-firstx + a*firsty) - c) / (a*a + 1))return thirdx,thirdy, fourthx,fourthyendfunction Drawing.complete_square(firstx,firsty, secondx,secondy, x,y)-- use x,y only to decide which side of the first edge to complete the square onlocal deltax = secondx-firstxlocal deltay = secondy-firstylocal thirdx = secondx+deltaylocal thirdy = secondy-deltaxif not geom.same_side(firstx,firsty, secondx,secondy, thirdx,thirdy, x,y) thendeltax = -deltaxdeltay = -deltaythirdx = secondx+deltaythirdy = secondy-deltaxendlocal fourthx = firstx+deltaylocal fourthy = firsty-deltaxreturn thirdx,thirdy, fourthx,fourthyendfunction Drawing.current_drawing(State)local x, y = App.mouse_x(), App.mouse_y()for drawing_index,drawing in ipairs(State.lines) doif drawing.mode == 'drawing' thenlocal line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenreturn drawing_index,drawing,line_cacheendendendreturn nilendfunction Drawing.select_shape_at_mouse(State)for drawing_index,drawing in ipairs(State.lines) doif drawing.mode == 'drawing' thenlocal x, y = App.mouse_x(), App.mouse_y()local line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)for i,shape in ipairs(drawing.shapes) doassert(shape)if geom.on_shape(mx,my, drawing, shape) thenreturn drawing,line_cache,i,shapeendendendendendendfunction Drawing.select_point_at_mouse(State)for drawing_index,drawing in ipairs(State.lines) doif drawing.mode == 'drawing' thenlocal x, y = App.mouse_x(), App.mouse_y()local line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)for i,point in ipairs(drawing.points) doassert(point)if Drawing.near(point, mx,my, State.width) thenreturn drawing_index,drawing,line_cache,i,pointendendendendendendfunction Drawing.select_drawing_at_mouse(State)for drawing_index,drawing in ipairs(State.lines) doif drawing.mode == 'drawing' thenlocal x, y = App.mouse_x(), App.mouse_y()local line_cache = State.line_cache[drawing_index]if Drawing.in_drawing(drawing, line_cache, x,y, State.left,State.right) thenreturn drawingendendendendfunction Drawing.contains_point(shape, p)if shape.mode == 'freehand' then-- not supportedelseif shape.mode == 'line' or shape.mode == 'manhattan' thenreturn shape.p1 == p or shape.p2 == pelseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' thenreturn table.find(shape.vertices, p)elseif shape.mode == 'circle' thenreturn shape.center == pelseif shape.mode == 'arc' thenreturn shape.center == p-- ugh, how to support angleselseif shape.mode == 'deleted' then-- already doneelseprint(shape.mode)assert(false)endendfunction Drawing.smoothen(shape)assert(shape.mode == 'freehand')for _=1,7 dofor i=2,#shape.points-1 dolocal a = shape.points[i-1]local b = shape.points[i]local c = shape.points[i+1]b.x = round((a.x + b.x + c.x)/3)b.y = round((a.y + b.y + c.y)/3)endendendfunction round(num)return math.floor(num+.5)endfunction Drawing.find_or_insert_point(points, x,y, width)-- check if UI would snap the two points togetherfor i,point in ipairs(points) doif Drawing.near(point, x,y, width) thenreturn iendendtable.insert(points, {x=x, y=y})return #pointsendfunction Drawing.near(point, x,y, width)local px,py = Drawing.pixels(x, width),Drawing.pixels(y, width)local cx,cy = Drawing.pixels(point.x, width), Drawing.pixels(point.y, width)return (cx-px)*(cx-px) + (cy-py)*(cy-py) < Same_point_distance*Same_point_distanceendfunction Drawing.pixels(n, width) -- parts to pixelsreturn math.floor(n*width/256)endfunction Drawing.coord(n, width) -- pixels to partsreturn math.floor(n*256/width)endfunction table.find(h, x)for k,v in pairs(h) doif v == x thenreturn kendendendif chord == 'C-p' and not App.mouse_down(1) thenfunction Drawing.mouse_release(State, x,y, mouse_button)if State.current_drawing_mode == 'move' thenState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilif State.lines.current_drawing thenState.lines.current_drawing.pending = {}State.lines.current_drawing = nilendelseif State.lines.current_drawing thenlocal drawing = State.lines.current_drawinglocal line_cache = State.line_cache[State.lines.current_drawing_index]if drawing.pending thenif drawing.pending.mode == nil then-- nothing pendingelseif drawing.pending.mode == 'freehand' then-- the last point added during update is good enoughDrawing.smoothen(drawing.pending)table.insert(drawing.shapes, drawing.pending)elseif drawing.pending.mode == 'line' thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thendrawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx,my, State.width)table.insert(drawing.shapes, drawing.pending)endelseif drawing.pending.mode == 'manhattan' thenlocal p1 = drawing.points[drawing.pending.p1]local mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thenif math.abs(mx-p1.x) > math.abs(my-p1.y) thendrawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, mx, p1.y, State.width)elsedrawing.pending.p2 = Drawing.find_or_insert_point(drawing.points, p1.x, my, State.width)endlocal p2 = drawing.points[drawing.pending.p2]App.mouse_move(State.left+Drawing.pixels(p2.x, State.width), line_cache.starty+Drawing.pixels(p2.y, State.width))table.insert(drawing.shapes, drawing.pending)endelseif drawing.pending.mode == 'polygon' thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thentable.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, mx,my, State.width))table.insert(drawing.shapes, drawing.pending)endelseif drawing.pending.mode == 'rectangle' thenassert(#drawing.pending.vertices <= 2)if #drawing.pending.vertices == 2 thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thenlocal first = drawing.points[drawing.pending.vertices[1]]local second = drawing.points[drawing.pending.vertices[2]]local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))table.insert(drawing.shapes, drawing.pending)endelse-- too few points; draw nothingendelseif drawing.pending.mode == 'square' thenassert(#drawing.pending.vertices <= 2)if #drawing.pending.vertices == 2 thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thenlocal first = drawing.points[drawing.pending.vertices[1]]local second = drawing.points[drawing.pending.vertices[2]]local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, thirdx,thirdy, State.width))table.insert(drawing.pending.vertices, Drawing.find_or_insert_point(drawing.points, fourthx,fourthy, State.width))table.insert(drawing.shapes, drawing.pending)endendelseif drawing.pending.mode == 'circle' thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thenlocal center = drawing.points[drawing.pending.center]drawing.pending.radius = round(geom.dist(center.x,center.y, mx,my))table.insert(drawing.shapes, drawing.pending)endelseif drawing.pending.mode == 'arc' thenlocal mx,my = Drawing.coord(x-State.left, State.width), Drawing.coord(y-line_cache.starty, State.width)if mx >= 0 and mx < 256 and my >= 0 and my < drawing.h thenlocal center = drawing.points[drawing.pending.center]drawing.pending.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, drawing.pending.end_angle)table.insert(drawing.shapes, drawing.pending)endelseif drawing.pending.mode == 'name' then-- drop itelseprint(drawing.pending.mode)assert(false)endState.lines.current_drawing.pending = {}State.lines.current_drawing = nilendendendif State.current_drawing_mode == 'move' thenState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilif State.lines.current_drawing thenState.lines.current_drawing.pending = {}State.lines.current_drawing = nil-- all the action is in mouse_releaseelseif State.current_drawing_mode == 'name' then-- nothingelseprint(State.current_drawing_mode)assert(false)endend-- a couple of operations on drawings need to constantly check the state of the mousefunction Drawing.update(State)if State.lines.current_drawing == nil then return endlocal drawing = State.lines.current_drawinglocal line_cache = State.line_cache[State.lines.current_drawing_index]assert(drawing.mode == 'drawing')local pmx, pmy = App.mouse_x(), App.mouse_y()local mx = Drawing.coord(pmx-State.left, State.width)local my = Drawing.coord(pmy-line_cache.starty, State.width)if App.mouse_down(1) thenif Drawing.in_drawing(drawing, line_cache, pmx,pmy, State.left,State.right) thenif drawing.pending.mode == 'freehand' thentable.insert(drawing.pending.points, {x=mx, y=my})elseif drawing.pending.mode == 'move' thendrawing.pending.target_point.x = mxdrawing.pending.target_point.y = myDrawing.relax_constraints(drawing, drawing.pending.target_point_index)endendelseif State.current_drawing_mode == 'move' thenif Drawing.in_drawing(drawing, line_cache, pmx, pmy, State.left,State.right) thendrawing.pending.target_point.x = mxdrawing.pending.target_point.y = myDrawing.relax_constraints(drawing, drawing.pending.target_point_index)endelse-- do nothingendendfunction Drawing.relax_constraints(drawing, p)for _,shape in ipairs(drawing.shapes) doif shape.mode == 'manhattan' thenif shape.p1 == p thenshape.mode = 'line'elseif shape.p2 == p thenshape.mode = 'line'endelseif shape.mode == 'rectangle' or shape.mode == 'square' thenfor _,v in ipairs(shape.vertices) doif v == p thenshape.mode = 'polygon'endendendendendelseif State.current_drawing_mode == 'name' then-- after mouse_releasefunction Drawing.mouse_press(State, drawing_index, x,y, mouse_button)local drawing = State.lines[drawing_index]local line_cache = State.line_cache[drawing_index]local cx = Drawing.coord(x-State.left, State.width)local cy = Drawing.coord(y-line_cache.starty, State.width)if State.current_drawing_mode == 'freehand' thendrawing.pending = {mode=State.current_drawing_mode, points={{x=cx, y=cy}}}elseif State.current_drawing_mode == 'line' or State.current_drawing_mode == 'manhattan' thenlocal j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)drawing.pending = {mode=State.current_drawing_mode, p1=j}elseif State.current_drawing_mode == 'polygon' or State.current_drawing_mode == 'rectangle' or State.current_drawing_mode == 'square' thenlocal j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)drawing.pending = {mode=State.current_drawing_mode, vertices={j}}elseif State.current_drawing_mode == 'circle' thenlocal j = Drawing.find_or_insert_point(drawing.points, cx, cy, State.width)drawing.pending = {mode=State.current_drawing_mode, center=j}elseif State.current_drawing_mode == 'move' thenlocal drawing = State.lines[drawing_index]local line_cache = State.line_cache[drawing_index]pmx,pmy = px(mx), py(my)local shape = drawing.pendingif shape.mode == nil then-- nothing pendingelseif shape.mode == 'freehand' thenlocal shape_copy = deepcopy(shape)Drawing.smoothen(shape_copy)Drawing.draw_shape(drawing, shape_copy, top, left,right)elseif shape.mode == 'line' thenif mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlocal p1 = drawing.points[shape.p1]love.graphics.line(px(p1.x),py(p1.y), pmx,pmy)elseif shape.mode == 'manhattan' thenif mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlocal p1 = drawing.points[shape.p1]if math.abs(mx-p1.x) > math.abs(my-p1.y) thenlove.graphics.line(px(p1.x),py(p1.y), pmx, py(p1.y))elselove.graphics.line(px(p1.x),py(p1.y), px(p1.x),pmy)endelseif shape.mode == 'polygon' then-- don't close the loop on a pending polygonlocal prev = nilfor _,point in ipairs(shape.vertices) dolocal curr = drawing.points[point]if prev thenlove.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))endprev = currendlove.graphics.line(px(prev.x),py(prev.y), pmx,pmy)elseif shape.mode == 'rectangle' thenlocal first = drawing.points[shape.vertices[1]]if #shape.vertices == 1 thenlove.graphics.line(px(first.x),py(first.y), pmx,pmy)returnendlocal second = drawing.points[shape.vertices[2]]local thirdx,thirdy, fourthx,fourthy = Drawing.complete_rectangle(first.x,first.y, second.x,second.y, mx,my)love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))elseif shape.mode == 'square' thenlocal first = drawing.points[shape.vertices[1]]if #shape.vertices == 1 thenlove.graphics.line(px(first.x),py(first.y), pmx,pmy)returnendlocal second = drawing.points[shape.vertices[2]]local thirdx,thirdy, fourthx,fourthy = Drawing.complete_square(first.x,first.y, second.x,second.y, mx,my)love.graphics.line(px(first.x),py(first.y), px(second.x),py(second.y))love.graphics.line(px(second.x),py(second.y), px(thirdx),py(thirdy))love.graphics.line(px(thirdx),py(thirdy), px(fourthx),py(fourthy))love.graphics.line(px(fourthx),py(fourthy), px(first.x),py(first.y))elseif shape.mode == 'circle' thenlocal center = drawing.points[shape.center]if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendlocal cx,cy = px(center.x), py(center.y)love.graphics.circle('line', cx,cy, geom.dist(cx,cy, App.mouse_x(),App.mouse_y()))elseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]if mx < 0 or mx >= 256 or my < 0 or my >= drawing.h thenreturnendshape.end_angle = geom.angle_with_hint(center.x,center.y, mx,my, shape.end_angle)local cx,cy = px(center.x), py(center.y)love.graphics.arc('line', 'open', cx,cy, Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)elseif shape.mode == 'move' then-- nothing pending; changes are immediately committedelseif shape.mode == 'name' then-- nothing pending; changes are immediately committedelseprint(shape.mode)assert(false)endendfunction Drawing.in_drawing(drawing, line_cache, x,y, left,right)if line_cache.starty == nil then return false end -- outside current pagelocal width = right-leftreturn y >= line_cache.starty and y < line_cache.starty + Drawing.pixels(drawing.h, width) and x >= left and x < rightendpmx,pmy = px(mx), py(my)-- primitives for editing drawingsDrawing = {}require 'drawing_tests'-- All drawings span 100% of some conceptual 'page width' and divide it up-- into 256 parts.function Drawing.draw(State, line_index, y)local line = State.lines[line_index]local line_cache = State.line_cache[line_index]line_cache.starty = ylocal pmx,pmy = App.mouse_x(), App.mouse_y()if pmx < State.right and pmy > line_cache.starty and pmy < line_cache.starty+Drawing.pixels(line.h, State.width) thenApp.color(Icon_color)love.graphics.rectangle('line', State.left,line_cache.starty, State.width,Drawing.pixels(line.h, State.width))if icon[State.current_drawing_mode] thenicon[State.current_drawing_mode](State.right-22, line_cache.starty+4)elseicon[State.previous_drawing_mode](State.right-22, line_cache.starty+4)endif App.mouse_down(1) and love.keyboard.isDown('h') thendraw_help_with_mouse_pressed(State, line_index)returnendendif line.show_help thendraw_help_without_mouse_pressed(State, line_index)returnendlocal mx = Drawing.coord(pmx-State.left, State.width)local my = Drawing.coord(pmy-line_cache.starty, State.width)for _,shape in ipairs(line.shapes) doassert(shape)if geom.on_shape(mx,my, line, shape) thenApp.color(Focus_stroke_color)elseApp.color(Stroke_color)endDrawing.draw_shape(line, shape, line_cache.starty, State.left,State.right)endlocal function px(x) return Drawing.pixels(x, State.width)+State.left endlocal function py(y) return Drawing.pixels(y, State.width)+line_cache.starty endfor i,p in ipairs(line.points) doif p.deleted == nil thenif Drawing.near(p, mx,my, State.width) thenApp.color(Focus_stroke_color)love.graphics.circle('line', px(p.x),py(p.y), Same_point_distance)elseApp.color(Stroke_color)love.graphics.circle('fill', px(p.x),py(p.y), 2)endif p.name then-- TODO: cliplocal x,y = px(p.x)+5, py(p.y)+5love.graphics.print(p.name, x,y)if State.current_drawing_mode == 'name' and i == line.pending.target_point then-- create a faint red box for the nameApp.color(Current_name_background_color)local name_text-- TODO: avoid computing name width on every repaintif p.name == '' thenname_text = State.emelsename_text = App.newText(love.graphics.getFont(), p.name)endlove.graphics.rectangle('fill', x,y, App.width(name_text), State.line_height)endendendendApp.color(Current_stroke_color)Drawing.draw_pending_shape(line, line_cache.starty, State.left,State.right)endfunction Drawing.draw_shape(drawing, shape, top, left,right)local width = right-leftlocal function px(x) return Drawing.pixels(x, width)+left endlocal function py(y) return Drawing.pixels(y, width)+top endif shape.mode == 'freehand' thenlocal prev = nilfor _,point in ipairs(shape.points) doif prev thenlove.graphics.line(px(prev.x),py(prev.y), px(point.x),py(point.y))endprev = pointendelseif shape.mode == 'line' or shape.mode == 'manhattan' thenlocal p1 = drawing.points[shape.p1]local p2 = drawing.points[shape.p2]love.graphics.line(px(p1.x),py(p1.y), px(p2.x),py(p2.y))elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' thenlocal prev = nilfor _,point in ipairs(shape.vertices) dolocal curr = drawing.points[point]if prev thenlove.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))endprev = currend-- close the looplocal curr = drawing.points[shape.vertices[1]]love.graphics.line(px(prev.x),py(prev.y), px(curr.x),py(curr.y))elseif shape.mode == 'circle' then-- TODO: cliplocal center = drawing.points[shape.center]love.graphics.circle('line', px(center.x),py(center.y), Drawing.pixels(shape.radius, width))elseif shape.mode == 'arc' thenlocal center = drawing.points[shape.center]love.graphics.arc('line', 'open', px(center.x),py(center.y), Drawing.pixels(shape.radius, width), shape.start_angle, shape.end_angle, 360)elseif shape.mode == 'deleted' then-- ignoreelseprint(shape.mode)assert(false)endendfunction Drawing.draw_pending_shape(drawing, top, left,right)local width = right-leftlocal pmx,pmy = App.mouse_x(), App.mouse_y()local function px(x) return Drawing.pixels(x, width)+left endlocal function py(y) return Drawing.pixels(y, width)+top endlocal mx = Drawing.coord(pmx-left, width)local my = Drawing.coord(pmy-top, width)-- recreate pixels from coords to precisely mimic how the drawing will look
Current_time = Current_time + 3.1edit.run_after_text_input(Editor_state, 'A')edit.run_after_keychord(Editor_state, 'return')check_eq(p2.name, 'A', 'F - test_undo_name_point/baseline')check_eq(#Editor_state.history, 3, 'F - test_undo_name_point/baseline/history:2')check_eq(Editor_state.next_history, 4, 'F - test_undo_name_point/baseline/next_history')--? print('b', Editor_state.lines.current_drawing)-- undoedit.run_after_keychord(Editor_state, 'C-z')local drawing = Editor_state.lines[1]local p2 = drawing.points[drawing.shapes[1].p2]check_eq(Editor_state.next_history, 3, 'F - test_undo_name_point/next_history')check_eq(p2.name, '', 'F - test_undo_name_point') -- not quite what it was before, but close enough-- wait until saveedit.run_after_text_input(Editor_state, 'p') -- add point-- third pointedit.run_after_mouse_release(Editor_state, Editor_state.left+14, Editor_state.top+Drawing_padding_top+16, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_delete_point_from_polygon/baseline/#shapes')check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/baseline/mode')check_eq(#drawing.shapes[1].vertices, 3, 'F - test_delete_point_from_polygon/baseline/vertices')-- hover on a point and deleteApp.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)edit.run_after_keychord(Editor_state, 'C-d')-- there's < 3 points left, so the whole polygon is deletedcheck_eq(drawing.shapes[1].mode, 'deleted', 'F - test_delete_point_from_polygon')endfunction test_undo_name_point()io.write('\ntest_undo_name_point')-- create a drawing with a lineApp.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.filename = 'foo'Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)Editor_state.current_drawing_mode = 'line'edit.draw(Editor_state)-- draw a lineedit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_undo_name_point/baseline/#shapes')check_eq(#drawing.points, 2, 'F - test_undo_name_point/baseline/#points')check_eq(drawing.shapes[1].mode, 'line', 'F - test_undo_name_point/baseline/shape:1')local p1 = drawing.points[drawing.shapes[1].p1]local p2 = drawing.points[drawing.shapes[1].p2]check_eq(p1.x, 5, 'F - test_undo_name_point/baseline/p1:x')check_eq(p1.y, 6, 'F - test_undo_name_point/baseline/p1:y')check_eq(p2.x, 35, 'F - test_undo_name_point/baseline/p2:x')check_eq(p2.y, 36, 'F - test_undo_name_point/baseline/p2:y')check_nil(p2.name, 'F - test_undo_name_point/baseline/p2:name')check_eq(#Editor_state.history, 1, 'F - test_undo_name_point/baseline/history:1')--? print('a', Editor_state.lines.current_drawing)-- enter 'name' mode without moving the mouseedit.run_after_keychord(Editor_state, 'C-n')edit.run_after_text_input(Editor_state, 'g') -- polygon mode-- second pointApp.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)edit.run_after_text_input(Editor_state, 'p') -- add point-- fourth pointedit.run_after_mouse_release(Editor_state, Editor_state.left+14, Editor_state.top+Drawing_padding_top+16, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_delete_point_from_polygon/baseline/#shapes')check_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/baseline/mode')check_eq(#drawing.shapes[1].vertices, 4, 'F - test_delete_point_from_polygon/baseline/vertices')-- hover on a point and deleteApp.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)edit.run_after_keychord(Editor_state, 'C-d')-- just the one point is deletedcheck_eq(drawing.shapes[1].mode, 'polygon', 'F - test_delete_point_from_polygon/shape')check_eq(#drawing.shapes[1].vertices, 3, 'F - test_delete_point_from_polygon/vertices')endfunction test_delete_point_from_polygon()io.write('\ntest_delete_point_from_polygon')-- create a drawing with two lines connected at a pointApp.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)Editor_state.current_drawing_mode = 'line'edit.draw(Editor_state)-- first pointedit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)edit.run_after_text_input(Editor_state, 'p') -- add point-- third pointApp.mouse_move(Editor_state.left+35, Editor_state.top+Drawing_padding_top+26)edit.run_after_text_input(Editor_state, 'g') -- polygon mode-- second pointApp.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)Current_time = Current_time + 3.1edit.run_after_text_input(Editor_state, 'A')check_eq(p2.name, 'A', 'F - test_name_point')-- still in 'name' modecheck_eq(Editor_state.current_drawing_mode, 'name', 'F - test_name_point/mode:2')-- exit 'name' modeedit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_name_point/mode:3')check_eq(p2.name, 'A', 'F - test_name_point')-- wait until saveedit.run_after_text_input(Editor_state, 'p')-- release (decides which side of first edge to draw square on)edit.run_after_mouse_release(Editor_state, Editor_state.left+15, Editor_state.top+Drawing_padding_top+26, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_draw_square/#shapes')check_eq(#drawing.points, 5, 'F - test_draw_square/#points') -- currently includes every point addedcheck_eq(drawing.shapes[1].mode, 'square', 'F - test_draw_square/shape_mode')check_eq(#drawing.shapes[1].vertices, 4, 'F - test_draw_square/vertices')local p = drawing.points[drawing.shapes[1].vertices[1]]check_eq(p.x, 35, 'F - test_draw_square/p1:x')check_eq(p.y, 36, 'F - test_draw_square/p1:y')local p = drawing.points[drawing.shapes[1].vertices[2]]check_eq(p.x, 65, 'F - test_draw_square/p2:x')check_eq(p.y, 66, 'F - test_draw_square/p2:y')local p = drawing.points[drawing.shapes[1].vertices[3]]check_eq(p.x, 35, 'F - test_draw_square/p3:x')check_eq(p.y, 96, 'F - test_draw_square/p3:y')local p = drawing.points[drawing.shapes[1].vertices[4]]check_eq(p.x, 5, 'F - test_draw_square/p4:x')check_eq(p.y, 66, 'F - test_draw_square/p4:y')endfunction test_name_point()io.write('\ntest_name_point')-- create a drawing with a lineApp.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.filename = 'foo'Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)Editor_state.current_drawing_mode = 'line'edit.draw(Editor_state)-- draw a lineedit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)edit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_name_point/baseline/#shapes')check_eq(#drawing.points, 2, 'F - test_name_point/baseline/#points')check_eq(drawing.shapes[1].mode, 'line', 'F - test_name_point/baseline/shape:1')local p1 = drawing.points[drawing.shapes[1].p1]local p2 = drawing.points[drawing.shapes[1].p2]check_eq(p1.x, 5, 'F - test_name_point/baseline/p1:x')check_eq(p1.y, 6, 'F - test_name_point/baseline/p1:y')check_eq(p2.x, 35, 'F - test_name_point/baseline/p2:x')check_eq(p2.y, 36, 'F - test_name_point/baseline/p2:y')check_nil(p2.name, 'F - test_name_point/baseline/p2:name')-- enter 'name' mode without moving the mouseedit.run_after_keychord(Editor_state, 'C-n')check_eq(Editor_state.current_drawing_mode, 'name', 'F - test_name_point/mode:1')edit.run_after_text_input(Editor_state, 'p')-- override second point/first edgeApp.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+66)edit.run_after_text_input(Editor_state, 's') -- square mode-- second point/first edgeApp.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)edit.run_after_text_input(Editor_state, 'p')local drawing = Editor_state.lines[1]check_eq(#drawing.points, 3, 'F - test_draw_rectangle_intermediate/#points') -- currently includes every point addedlocal pending = drawing.pendingcheck_eq(pending.mode, 'rectangle', 'F - test_draw_rectangle_intermediate/shape_mode')check_eq(#pending.vertices, 2, 'F - test_draw_rectangle_intermediate/vertices')local p = drawing.points[pending.vertices[1]]check_eq(p.x, 35, 'F - test_draw_rectangle_intermediate/p1:x')check_eq(p.y, 36, 'F - test_draw_rectangle_intermediate/p1:y')local p = drawing.points[pending.vertices[2]]check_eq(p.x, 75, 'F - test_draw_rectangle_intermediate/p2:x')check_eq(p.y, 76, 'F - test_draw_rectangle_intermediate/p2:y')-- outline of rectangle is drawn based on where the mouse is, but we can't check that so farendfunction test_draw_square()io.write('\ntest_draw_square')-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)edit.draw(Editor_state)check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_square/baseline/drawing_mode')check_eq(#Editor_state.lines, 2, 'F - test_draw_square/baseline/#lines')check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_square/baseline/mode')check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_square/baseline/y')check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_square/baseline/y')check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_square/baseline/#shapes')-- first pointedit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)edit.run_after_text_input(Editor_state, 'p')-- override second point/first edgeApp.mouse_move(Editor_state.left+75, Editor_state.top+Drawing_padding_top+76)edit.run_after_text_input(Editor_state, 'r') -- rectangle mode-- second point/first edgeApp.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)edit.run_after_text_input(Editor_state, 'p')-- release (decides 'thickness' of rectangle perpendicular to first edge)edit.run_after_mouse_release(Editor_state, Editor_state.left+15, Editor_state.top+Drawing_padding_top+26, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_draw_rectangle/#shapes')check_eq(#drawing.points, 5, 'F - test_draw_rectangle/#points') -- currently includes every point addedlocal shape = drawing.shapes[1]check_eq(shape.mode, 'rectangle', 'F - test_draw_rectangle/shape_mode')check_eq(#shape.vertices, 4, 'F - test_draw_rectangle/vertices')local p = drawing.points[shape.vertices[1]]check_eq(p.x, 35, 'F - test_draw_rectangle/p1:x')check_eq(p.y, 36, 'F - test_draw_rectangle/p1:y')local p = drawing.points[shape.vertices[2]]check_eq(p.x, 75, 'F - test_draw_rectangle/p2:x')check_eq(p.y, 76, 'F - test_draw_rectangle/p2:y')local p = drawing.points[shape.vertices[3]]check_eq(p.x, 70, 'F - test_draw_rectangle/p3:x')check_eq(p.y, 81, 'F - test_draw_rectangle/p3:y')local p = drawing.points[shape.vertices[4]]check_eq(p.x, 30, 'F - test_draw_rectangle/p4:x')check_eq(p.y, 41, 'F - test_draw_rectangle/p4:y')endfunction test_draw_rectangle_intermediate()io.write('\ntest_draw_rectangle_intermediate')-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)edit.draw(Editor_state)check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_rectangle_intermediate/baseline/drawing_mode')check_eq(#Editor_state.lines, 2, 'F - test_draw_rectangle_intermediate/baseline/#lines')check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_rectangle_intermediate/baseline/mode')check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_rectangle_intermediate/baseline/y')check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_rectangle_intermediate/baseline/y')check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_rectangle_intermediate/baseline/#shapes')-- first pointedit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)edit.run_after_text_input(Editor_state, 'p')-- override second point/first edgeApp.mouse_move(Editor_state.left+75, Editor_state.top+Drawing_padding_top+76)edit.run_after_text_input(Editor_state, 'r') -- rectangle mode-- second point/first edgeApp.mouse_move(Editor_state.left+42, Editor_state.top+Drawing_padding_top+45)edit.run_after_text_input(Editor_state, 'p') -- add point-- final pointedit.run_after_mouse_release(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+26, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_draw_polygon/#shapes')check_eq(#drawing.points, 3, 'F - test_draw_polygon/vertices')local shape = drawing.shapes[1]check_eq(shape.mode, 'polygon', 'F - test_draw_polygon/shape_mode')check_eq(#shape.vertices, 3, 'F - test_draw_polygon/vertices')local p = drawing.points[shape.vertices[1]]check_eq(p.x, 5, 'F - test_draw_polygon/p1:x')check_eq(p.y, 6, 'F - test_draw_polygon/p1:y')local p = drawing.points[shape.vertices[2]]check_eq(p.x, 65, 'F - test_draw_polygon/p2:x')check_eq(p.y, 36, 'F - test_draw_polygon/p2:y')local p = drawing.points[shape.vertices[3]]check_eq(p.x, 35, 'F - test_draw_polygon/p3:x')check_eq(p.y, 26, 'F - test_draw_polygon/p3:y')endfunction test_draw_rectangle()io.write('\ntest_draw_rectangle')-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)edit.draw(Editor_state)check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_rectangle/baseline/drawing_mode')check_eq(#Editor_state.lines, 2, 'F - test_draw_rectangle/baseline/#lines')check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_rectangle/baseline/mode')check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_rectangle/baseline/y')check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_rectangle/baseline/y')check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_rectangle/baseline/#shapes')-- first pointedit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)edit.run_after_text_input(Editor_state, 'g') -- polygon mode-- second pointApp.mouse_move(Editor_state.left+65, Editor_state.top+Drawing_padding_top+36)edit.run_after_text_input(Editor_state, 'a') -- arc modeedit.run_after_mouse_release(Editor_state, Editor_state.left+35+50, Editor_state.top+Drawing_padding_top+36+50, 1) -- 45°local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_draw_arc/#shapes')check_eq(#drawing.points, 1, 'F - test_draw_arc/#points')check_eq(drawing.shapes[1].mode, 'arc', 'F - test_draw_horizontal_line/shape_mode')local arc = drawing.shapes[1]check_eq(arc.radius, 30, 'F - test_draw_arc/radius')local center = drawing.points[arc.center]check_eq(center.x, 35, 'F - test_draw_arc/center:x')check_eq(center.y, 36, 'F - test_draw_arc/center:y')check_eq(arc.start_angle, 0, 'F - test_draw_arc/start:angle')check_eq(arc.end_angle, math.pi/4, 'F - test_draw_arc/end:angle')endfunction test_draw_polygon()io.write('\ntest_draw_polygon')-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)edit.draw(Editor_state)check_eq(Editor_state.current_drawing_mode, 'line', 'F - test_draw_polygon/baseline/drawing_mode')check_eq(#Editor_state.lines, 2, 'F - test_draw_polygon/baseline/#lines')check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_polygon/baseline/mode')check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_polygon/baseline/y')check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_polygon/baseline/y')check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_polygon/baseline/#shapes')-- first pointedit.run_after_mouse_press(Editor_state, Editor_state.left+5, Editor_state.top+Drawing_padding_top+6, 1)edit.run_after_text_input(Editor_state, 'o')edit.run_after_mouse_release(Editor_state, Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36, 1)local drawing = Editor_state.lines[1]check_eq(#drawing.shapes, 1, 'F - test_draw_circle_mid_stroke/#shapes')check_eq(#drawing.points, 1, 'F - test_draw_circle_mid_stroke/#points')check_eq(drawing.shapes[1].mode, 'circle', 'F - test_draw_horizontal_line/shape_mode')check_eq(drawing.shapes[1].radius, 30, 'F - test_draw_circle_mid_stroke/radius')local center = drawing.points[drawing.shapes[1].center]check_eq(center.x, 35, 'F - test_draw_circle_mid_stroke/center:x')check_eq(center.y, 36, 'F - test_draw_circle_mid_stroke/center:y')endfunction test_draw_arc()io.write('\ntest_draw_arc')-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)Editor_state.current_drawing_mode = 'circle'edit.draw(Editor_state)check_eq(#Editor_state.lines, 2, 'F - test_draw_arc/baseline/#lines')check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_arc/baseline/mode')check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_arc/baseline/y')check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_arc/baseline/y')check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_arc/baseline/#shapes')-- draw an arcedit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)App.mouse_move(Editor_state.left+35+30, Editor_state.top+Drawing_padding_top+36)-- no change to text either because we didn't run the text_input eventendfunction test_draw_circle_mid_stroke()io.write('\ntest_draw_circle_mid_stroke')-- display a drawing followed by a line of text (you shouldn't ever have a drawing right at the end)App.screen.init{width=Test_margin_left+256, height=300} -- drawing coordinates 1:1 with pixelsEditor_state = edit.initialize_test_state()Editor_state.lines = load_array{'```lines', '```', ''}Text.redraw_all(Editor_state)Editor_state.current_drawing_mode = 'line'edit.draw(Editor_state)check_eq(#Editor_state.lines, 2, 'F - test_draw_circle_mid_stroke/baseline/#lines')check_eq(Editor_state.lines[1].mode, 'drawing', 'F - test_draw_circle_mid_stroke/baseline/mode')check_eq(Editor_state.line_cache[1].starty, Editor_state.top+Drawing_padding_top, 'F - test_draw_circle_mid_stroke/baseline/y')check_eq(Editor_state.lines[1].h, 128, 'F - test_draw_circle_mid_stroke/baseline/y')check_eq(#Editor_state.lines[1].shapes, 0, 'F - test_draw_circle_mid_stroke/baseline/#shapes')-- draw a circleApp.mouse_move(Editor_state.left+4, Editor_state.top+Drawing_padding_top+4) -- hover on drawingedit.run_after_mouse_press(Editor_state, Editor_state.left+35, Editor_state.top+Drawing_padding_top+36, 1)
edit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'up')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, 'F - test_search_wrap_upwards/1/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'F - test_search_wrap_upwards/1/cursor:pos')endedit.run_after_text_input(Editor_state, 'a')edit.run_after_keychord(Editor_state, 'return')-- cursor wrapscheck_eq(Editor_state.cursor1.line, 1, 'F - test_search_wrap/1/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_search_wrap/1/cursor:pos')endfunction test_search_wrap_upwards()io.write('\ntest_search_wrap_upwards')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc abd'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- search upwards for a stringedit.run_after_keychord(Editor_state, 'C-f')edit.run_after_text_input(Editor_state, 'a')-- search for previous occurrenceedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.cursor1.line, 1, 'F - test_search_upwards/2/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_search_upwards/2/cursor:pos')endfunction test_search_wrap()io.write('\ntest_search_wrap')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=3}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f')edit.run_after_text_input(Editor_state, 'de')edit.run_after_keychord(Editor_state, 'down')edit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.cursor1.line, 4, 'F - test_search/2/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_search/2/cursor:pos')endfunction test_search_upwards()io.write('\ntest_search_upwards')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc abd'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- search for a stringedit.run_after_keychord(Editor_state, 'C-f')edit.run_after_text_input(Editor_state, 'd')edit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.cursor1.line, 2, 'F - test_search/1/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_search/1/cursor:pos')-- reset cursorEditor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}-- search for second occurrenceedit.run_after_keychord(Editor_state, 'C-f')edit.run_after_text_input(Editor_state, 'x')check_eq(Editor_state.lines[1].data, 'xbc', 'F - test_undo_restores_selection/baseline')check_nil(Editor_state.selection1.line, 'F - test_undo_restores_selection/baseline:selection')-- undoedit.run_after_keychord(Editor_state, 'C-z')edit.run_after_keychord(Editor_state, 'C-z')-- selection is restoredcheck_eq(Editor_state.selection1.line, 1, 'F - test_undo_restores_selection/line')check_eq(Editor_state.selection1.pos, 2, 'F - test_undo_restores_selection/pos')endedit.run_after_text_input(Editor_state, 'g')check_eq(Editor_state.cursor1.line, 2, 'F - test_undo_insert_text/baseline/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'F - test_undo_insert_text/baseline/cursor:pos')edit.run_after_text_input(Editor_state, 's')edit.run_after_text_input(Editor_state, 't')edit.run_after_text_input(Editor_state, 'u')check_eq(Editor_state.cursor1.pos, 28, 'F - test_position_cursor_on_recently_edited_wrapping_line/cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'stu', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3')-- try to move the cursor earlier in the third screen line by clicking the mouseedit.run_after_text_input(Editor_state, 'j')edit.run_after_text_input(Editor_state, 'k')edit.run_after_text_input(Editor_state, 'l')check_eq(Editor_state.screen_top1.line, 2, 'F - test_typing_on_bottom_line_scrolls_down/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_typing_on_bottom_line_scrolls_down/cursor:line')check_eq(Editor_state.cursor1.pos, 7, 'F - test_typing_on_bottom_line_scrolls_down/cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'F - test_typing_on_bottom_line_scrolls_down/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij', 'F - test_typing_on_bottom_line_scrolls_down/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'F - test_typing_on_bottom_line_scrolls_down/screen:3')endfunction test_left_arrow_scrolls_up_in_wrapped_line()io.write('\ntest_left_arrow_scrolls_up_in_wrapped_line')-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=5}Editor_state.screen_bottom1 = {}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'F - test_left_arrow_scrolls_up_in_wrapped_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_left_arrow_scrolls_up_in_wrapped_line/baseline/screen:2')-- after hitting the left arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'left')y = Editor_state.topApp.screen.check(y, 'ghi ', 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen:3')check_eq(Editor_state.screen_top1.line, 3, 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen_top')check_eq(Editor_state.screen_top1.pos, 1, 'F - test_left_arrow_scrolls_up_in_wrapped_line/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_left_arrow_scrolls_up_in_wrapped_line/cursor:line')check_eq(Editor_state.cursor1.pos, 4, 'F - test_left_arrow_scrolls_up_in_wrapped_line/cursor:pos')endfunction test_right_arrow_scrolls_down_in_wrapped_line()io.write('\ntest_right_arrow_scrolls_down_in_wrapped_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_right_arrow_scrolls_down_in_wrapped_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_right_arrow_scrolls_down_in_wrapped_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'F - test_right_arrow_scrolls_down_in_wrapped_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the right arrow the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'right')check_eq(Editor_state.screen_top1.line, 2, 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_right_arrow_scrolls_down_in_wrapped_line/cursor:line')check_eq(Editor_state.cursor1.pos, 6, 'F - test_right_arrow_scrolls_down_in_wrapped_line/cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_right_arrow_scrolls_down_in_wrapped_line/screen:3')endfunction test_home_scrolls_up_in_wrapped_line()io.write('\ntest_home_scrolls_up_in_wrapped_line')-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=3, pos=5}Editor_state.screen_bottom1 = {}-- cursor is at top of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'F - test_home_scrolls_up_in_wrapped_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_home_scrolls_up_in_wrapped_line/baseline/screen:2')-- after hitting home the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'home')y = Editor_state.topApp.screen.check(y, 'ghi ', 'F - test_home_scrolls_up_in_wrapped_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_home_scrolls_up_in_wrapped_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_home_scrolls_up_in_wrapped_line/screen:3')check_eq(Editor_state.screen_top1.line, 3, 'F - test_home_scrolls_up_in_wrapped_line/screen_top')check_eq(Editor_state.screen_top1.pos, 1, 'F - test_home_scrolls_up_in_wrapped_line/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_home_scrolls_up_in_wrapped_line/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_home_scrolls_up_in_wrapped_line/cursor:pos')endfunction test_end_scrolls_down_in_wrapped_line()io.write('\ntest_end_scrolls_down_in_wrapped_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- cursor is at bottom right of screenEditor_state.cursor1 = {line=3, pos=5}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_end_scrolls_down_in_wrapped_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_end_scrolls_down_in_wrapped_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'F - test_end_scrolls_down_in_wrapped_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting end the screen scrolls down by one lineedit.run_after_keychord(Editor_state, 'end')check_eq(Editor_state.screen_top1.line, 2, 'F - test_end_scrolls_down_in_wrapped_line/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_end_scrolls_down_in_wrapped_line/cursor:line')check_eq(Editor_state.cursor1.pos, 8, 'F - test_end_scrolls_down_in_wrapped_line/cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'F - test_end_scrolls_down_in_wrapped_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'F - test_end_scrolls_down_in_wrapped_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_end_scrolls_down_in_wrapped_line/screen:3')endfunction test_position_cursor_on_recently_edited_wrapping_line()-- draw a line wrapping over 2 screen linesio.write('\ntest_position_cursor_on_recently_edited_wrapping_line')App.screen.init{width=100, height=200}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr ', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=25}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc def ghi ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl mno pqr ', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'xyz', 'F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3')-- add to the line until it's wrapping over 3 screen linesedit.run_after_text_input(Editor_state, 'a')check_eq(Editor_state.screen_top1.line, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')check_eq(Editor_state.cursor1.line, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')local y = Editor_state.topApp.screen.check(y, 'a', 'F - test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')endfunction test_typing_on_bottom_line_scrolls_down()io.write('\ntest_typing_on_bottom_line_scrolls_down')-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=4}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_typing_on_bottom_line_scrolls_down/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_typing_on_bottom_line_scrolls_down/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_typing_on_bottom_line_scrolls_down/baseline/screen:3')-- after typing something the line wraps and the screen scrolls downApp.screen.check(y, 'mno', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:3')endfunction test_up_arrow_moves_cursor()io.write('\ntest_up_arrow_moves_cursor')-- display the first 3 lines with the cursor on the bottom lineApp.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/baseline/screen:3')-- after hitting the up arrow the cursor moves up by 1 lineedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_moves_cursor/screen_top')check_eq(Editor_state.cursor1.line, 2, 'F - test_up_arrow_moves_cursor/cursor')-- the screen is unchangedy = Editor_state.topApp.screen.check(y, 'abc', 'F - test_up_arrow_moves_cursor/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_moves_cursor/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_moves_cursor/screen:3')endfunction test_up_arrow_scrolls_up_by_one_line()io.write('\ntest_up_arrow_scrolls_up_by_one_line')-- display the lines 2/3/4 with the cursor on line 2App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/screen_top')check_eq(Editor_state.cursor1.line, 1, 'F - test_up_arrow_scrolls_up_by_one_line/cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_by_one_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_by_one_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_by_one_line/screen:3')endfunction test_up_arrow_scrolls_up_by_one_screen_line()io.write('\ntest_up_arrow_scrolls_up_by_one_screen_line')-- display lines starting from second screen line of a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=6}Editor_state.screen_top1 = {line=3, pos=5}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2')-- after hitting the up arrow the screen scrolls up to first screen lineedit.run_after_keychord(Editor_state, 'up')y = Editor_state.topApp.screen.check(y, 'ghi ', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3')check_eq(Editor_state.screen_top1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')check_eq(Editor_state.screen_top1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos')endfunction test_up_arrow_scrolls_up_to_final_screen_line()io.write('\ntest_up_arrow_scrolls_up_to_final_screen_line')-- display lines starting just after a long lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up to final screen line of previous lineedit.run_after_keychord(Editor_state, 'up')y = Editor_state.topApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3')check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')check_eq(Editor_state.screen_top1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top')check_eq(Editor_state.cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos')endfunction test_up_arrow_scrolls_up_to_empty_line()io.write('\ntest_up_arrow_scrolls_up_to_empty_line')-- display a screenful of text with an empty line just above it outside the screenApp.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'', 'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3')-- after hitting the up arrow the screen scrolls up by one lineedit.run_after_keychord(Editor_state, 'up')check_eq(Editor_state.screen_top1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/screen_top')check_eq(Editor_state.cursor1.line, 1, 'F - test_up_arrow_scrolls_up_to_empty_line/cursor')y = Editor_state.top-- empty first liney = y + Editor_state.line_heightApp.screen.check(y, 'abc', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_up_arrow_scrolls_up_to_empty_line/screen:3')endfunction test_pageup()io.write('\ntest_pageup')App.screen.init{width=120, height=45}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}-- initially the last two lines are displayededit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'F - test_pageup/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_pageup/baseline/screen:2')-- after pageup the cursor goes to first lineedit.run_after_keychord(Editor_state, 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'F - test_pageup/screen_top')check_eq(Editor_state.cursor1.line, 1, 'F - test_pageup/cursor')y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_pageup/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_pageup/screen:2')endfunction test_pageup_scrolls_up_by_screen_line()io.write('\ntest_pageup_scrolls_up_by_screen_line')-- display the first three lines with the cursor on the bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi', 'jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/screen_top')check_eq(Editor_state.cursor1.line, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_pageup_scrolls_up_by_screen_line/cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_by_screen_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_pageup_scrolls_up_by_screen_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_pageup_scrolls_up_by_screen_line/screen:3')endfunction test_pageup_scrolls_up_from_middle_screen_line()io.write('\ntest_pageup_scrolls_up_from_middle_screen_line')-- display a few lines starting from the middle of a line (Editor_state.cursor1.pos > 1)App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi jkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=5}Editor_state.screen_top1 = {line=2, pos=5}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'mno', 'F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3') -- line wrapping includes trailing whitespace-- after hitting the page-up key the screen scrolls up to topedit.run_after_keychord(Editor_state, 'pageup')check_eq(Editor_state.screen_top1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/screen_top')check_eq(Editor_state.cursor1.line, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos')y = Editor_state.topApp.screen.check(y, 'abc ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi ', 'F - test_pageup_scrolls_up_from_middle_screen_line/screen:3')endfunction test_enter_on_bottom_line_scrolls_down()io.write('\ntest_enter_on_bottom_line_scrolls_down')-- display a few lines with cursor on bottom lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3')-- after hitting the enter key the screen scrolls downedit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.screen_top1.line, 2, 'F - test_enter_on_bottom_line_scrolls_down/screen_top')check_eq(Editor_state.cursor1.line, 4, 'F - test_enter_on_bottom_line_scrolls_down/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_enter_on_bottom_line_scrolls_down/cursor:pos')y = Editor_state.topApp.screen.check(y, 'def', 'F - test_enter_on_bottom_line_scrolls_down/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'g', 'F - test_enter_on_bottom_line_scrolls_down/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'hi', 'F - test_enter_on_bottom_line_scrolls_down/screen:3')endfunction test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom()io.write('\ntest_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom')-- display just the bottom line on screenApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=4, pos=2}Editor_state.screen_top1 = {line=4, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'jkl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1')-- after hitting the enter key the screen does not scroll downedit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.screen_top1.line, 4, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top')check_eq(Editor_state.cursor1.line, 5, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos')y = Editor_state.topApp.screen.check(y, 'j', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'kl', 'F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2')endfunction test_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom()io.write('\ntest_inserting_text_on_final_line_avoids_scrolling_down_when_not_at_bottom')-- display just an empty bottom line on screenApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', ''}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- after hitting the inserting_text key the screen does not scroll downApp.screen.check(y, 'kl', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen:1')y = y + Editor_state.line_heightcheck_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line')check_eq(Editor_state.cursor1.pos, 5, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos')y = Editor_state.topcheck_eq(Editor_state.screen_top1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top')check_eq(Editor_state.cursor1.line, 3, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos')-- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll upedit.run_after_keychord(Editor_state, 'down')App.screen.check(y, 'ghij', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3')-- after hitting pagedown the screen scrolls down to start of a long lineedit.run_after_keychord(Editor_state, 'pagedown')App.screen.check(y, 'def', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'abc', 'F - test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1')y = y + Editor_state.line_heightfunction test_pagedown_followed_by_down_arrow_does_not_scroll_screen_up()io.write('\ntest_pagedown_followed_by_down_arrow_does_not_scroll_screen_up')App.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghijkl', 'mno'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topedit.run_after_text_input(Editor_state, 'g')local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_edit_wrapping_text/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'de', 'F - test_edit_wrapping_text/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'fg', 'F - test_edit_wrapping_text/screen:3')endfunction test_insert_newline()io.write('\ntest_insert_newline')-- display a few linesApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_insert_newline/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_insert_newline/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_insert_newline/baseline/screen:3')-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.screen_top1.line, 1, 'F - test_insert_newline/screen_top')check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline/cursor:pos')y = Editor_state.topApp.screen.check(y, 'a', 'F - test_insert_newline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'bc', 'F - test_insert_newline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_insert_newline/screen:3')endfunction test_insert_newline_at_start_of_line()io.write('\ntest_insert_newline_at_start_of_line')-- display a lineApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- hitting the enter key splits the lineedit.run_after_keychord(Editor_state, 'return')check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_newline_at_start_of_line/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_insert_newline_at_start_of_line/cursor:pos')check_eq(Editor_state.lines[1].data, '', 'F - test_insert_newline_at_start_of_line/data:1')check_eq(Editor_state.lines[2].data, 'abc', 'F - test_insert_newline_at_start_of_line/data:2')endfunction test_insert_from_clipboard()io.write('\ntest_insert_from_clipboard')-- display a few linesApp.screen.init{width=Editor_state.left+30, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_insert_from_clipboard/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_insert_from_clipboard/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_insert_from_clipboard/baseline/screen:3')-- paste some text including a newline, check that new line is createdApp.clipboard = 'xy\nz'edit.run_after_keychord(Editor_state, 'C-v')check_eq(Editor_state.screen_top1.line, 1, 'F - test_insert_from_clipboard/screen_top')check_eq(Editor_state.cursor1.line, 2, 'F - test_insert_from_clipboard/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_insert_from_clipboard/cursor:pos')y = Editor_state.topApp.screen.check(y, 'axy', 'F - test_insert_from_clipboard/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'zbc', 'F - test_insert_from_clipboard/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_insert_from_clipboard/screen:3')edit.keychord_press(Editor_state, 'd', 'd')edit.text_input(Editor_state, 'D')edit.key_release(Editor_state, 'd')App.fake_key_release('lshift')-- selected text is deleted and replaced with the keycheck_nil(Editor_state.selection1.line, 'F - test_edit_with_shift_key_deletes_selection')check_eq(Editor_state.lines[1].data, 'Dbc', 'F - test_edit_with_shift_key_deletes_selection/data')endfunction test_copy_does_not_reset_selection()io.write('\ntest_copy_does_not_reset_selection')-- display a line of text with a selectionApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- copy selectionedit.run_after_keychord(Editor_state, 'C-c')check_eq(App.clipboard, 'a', 'F - test_copy_does_not_reset_selection/clipboard')-- selection is reset since shift key is not pressedcheck(Editor_state.selection1.line, 'F - test_copy_does_not_reset_selection')endfunction test_cut()io.write('\ntest_cut')-- display a line of text with some part selectedApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- press a keyedit.run_after_keychord(Editor_state, 'C-x')check_eq(App.clipboard, 'a', 'F - test_cut/clipboard')-- selected text is deletedcheck_eq(Editor_state.lines[1].data, 'bc', 'F - test_cut/data')endfunction test_paste_replaces_selection()io.write('\ntest_paste_replaces_selection')-- display a line of text with a selectionApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.selection1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- set clipboardApp.clipboard = 'xyz'-- paste selectionedit.run_after_keychord(Editor_state, 'C-v')-- selection is reset since shift key is not pressed-- selection includes the newline, so it's also deletedcheck_eq(Editor_state.lines[1].data, 'xyzdef', 'F - test_paste_replaces_selection')endfunction test_deleting_selection_may_scroll()io.write('\ntest_deleting_selection_may_scroll')-- display lines 2/3/4App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=3, pos=2}Editor_state.screen_top1 = {line=2, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'def', 'F - test_deleting_selection_may_scroll/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_deleting_selection_may_scroll/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'jkl', 'F - test_deleting_selection_may_scroll/baseline/screen:3')-- set up a selection starting above the currently displayed pageEditor_state.selection1 = {line=1, pos=2}-- delete selectionedit.run_after_keychord(Editor_state, 'backspace')-- page scrolls upcheck_eq(Editor_state.screen_top1.line, 1, 'F - test_deleting_selection_may_scroll')check_eq(Editor_state.lines[1].data, 'ahi', 'F - test_deleting_selection_may_scroll/data')endedit.run_after_text_input(Editor_state, 'x')-- selected text is deleted and replaced with the keycheck_eq(Editor_state.lines[1].data, 'xbc', 'F - test_edit_deletes_selection')endfunction test_edit_with_shift_key_deletes_selection()io.write('\ntest_edit_with_shift_key_deletes_selection')-- display a line of text with some part selectedApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- mimic precise keypresses for a capital letterApp.fake_key_press('lshift')edit.key_release(Editor_state, 'lshift')-- selection persists even after shift is releasecheck_eq(Editor_state.selection1.line, 1, 'F - test_select_text/selection:line')check_eq(Editor_state.selection1.pos, 1, 'F - test_select_text/selection:pos')check_eq(Editor_state.cursor1.line, 1, 'F - test_select_text/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_select_text/cursor:pos')endfunction test_cursor_movement_without_shift_resets_selection()io.write('\ntest_cursor_movement_without_shift_resets_selection')-- display a line of text with some part selectedApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.selection1 = {line=1, pos=2}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)-- press an arrow key without shiftedit.run_after_keychord(Editor_state, 'right')-- no change to data, selection is resetcheck_nil(Editor_state.selection1.line, 'F - test_cursor_movement_without_shift_resets_selection')check_eq(Editor_state.lines[1].data, 'abc', 'F - test_cursor_movement_without_shift_resets_selection/data')check_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line_takes_margins_into_account/cursor:pos')check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line_takes_margins_into_account/selection is empty to avoid perturbing future edits')endfunction test_draw_text_wrapping_within_word()-- arrange a screen line that needs to be split within a wordio.write('\ntest_draw_text_wrapping_within_word')App.screen.init{width=60, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abcd e fghijk', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abcd ', 'F - test_draw_text_wrapping_within_word/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'e fgh', 'F - test_draw_text_wrapping_within_word/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ijk', 'F - test_draw_text_wrapping_within_word/screen:3')endfunction test_draw_wrapping_text_containing_non_ascii()-- draw a long line containing non-ASCIIio.write('\ntest_draw_wrapping_text_containing_non_ascii')App.screen.init{width=60, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'madam I’m adam', 'xyz'} -- notice the non-ASCII apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'mad', 'F - test_draw_wrapping_text_containing_non_ascii/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'am I', 'F - test_draw_wrapping_text_containing_non_ascii/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, '’m a', 'F - test_draw_wrapping_text_containing_non_ascii/screen:3')endfunction test_click_on_wrapping_line()io.write('\ntest_click_on_wrapping_line')-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{"madam I'm adam"}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'F - test_click_on_wrapping_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ad", 'F - test_click_on_wrapping_line/baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen linecheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')check_eq(Editor_state.cursor1.pos, 12, 'F - test_click_on_wrapping_line/cursor:pos')endfunction test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen()io.write('\ntest_click_on_wrapping_line_rendered_from_partway_at_top_of_screen')-- display a wrapping line from its second screen lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{"madam I'm adam"}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=8}Editor_state.screen_top1 = {line=1, pos=7}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, "I'm ad", 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/baseline/screen:2')y = y + Editor_state.line_height-- click past end of second screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen linecheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/cursor:line')check_eq(Editor_state.cursor1.pos, 12, 'F - test_click_on_wrapping_line_rendered_from_partway_at_top_of_screen/cursor:pos')endfunction test_click_past_end_of_wrapping_line()io.write('\ntest_click_past_end_of_wrapping_line')-- display a wrapping lineApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{"madam I'm adam"}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'F - test_click_past_end_of_wrapping_line/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, "I'm ad", 'F - test_click_past_end_of_wrapping_line/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'am', 'F - test_click_past_end_of_wrapping_line/baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'F - test_click_past_end_of_wrapping_line/cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_wrapping_line_containing_non_ascii()io.write('\ntest_click_past_end_of_wrapping_line_containing_non_ascii')-- display a wrapping line containing non-ASCIIApp.screen.init{width=75, height=80}Editor_state = edit.initialize_test_state()-- 12345678901234Editor_state.lines = load_array{'madam I’m adam'} -- notice the non-ASCII apostropheText.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'madam ', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'I’m ad', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'am', 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/baseline/screen:3')y = y + Editor_state.line_height-- click past the end of itedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of linecheck_eq(Editor_state.cursor1.pos, 15, 'F - test_click_past_end_of_wrapping_line_containing_non_ascii/cursor') -- one more than the number of UTF-8 code-pointsendfunction test_click_past_end_of_word_wrapping_line()io.write('\ntest_click_past_end_of_word_wrapping_line')-- display a long line wrapping at a word boundary on a screen of more realistic lengthApp.screen.init{width=160, height=80}Editor_state = edit.initialize_test_state()-- 0 1 2-- 123456789012345678901Editor_state.lines = load_array{'the quick brown fox jumped over the lazy dog'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'the quick brown fox ', 'F - test_click_past_end_of_word_wrapping_line/baseline/screen:1')y = y + Editor_state.line_height-- click past the end of the screen lineedit.run_after_mouse_click(Editor_state, App.screen.width-2,y-2, 1)-- cursor moves to end of screen linecheck_eq(Editor_state.cursor1.pos, 20, 'F - test_click_past_end_of_word_wrapping_line/cursor')function test_click_on_wrapping_line_takes_margins_into_account()io.write('\ntest_click_on_wrapping_line_takes_margins_into_account')-- display two lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=20}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_wrapping_line/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_on_wrapping_line/cursor:pos')check_nil(Editor_state.selection1.line, 'F - test_click_on_wrapping_line/selection is empty to avoid perturbing future edits')endfunction test_click_on_wrapping_line()io.write('\ntest_click_on_wrapping_line')-- display two lines with cursor on one of themApp.screen.init{width=50, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi jkl mno pqr stu'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=20}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_on_empty_line/cursor')endfunction test_draw_text()io.write('\ntest_draw_text')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def', 'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_draw_text/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def', 'F - test_draw_text/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_draw_text/screen:3')endfunction test_draw_wrapping_text()io.write('\ntest_draw_wrapping_text')App.screen.init{width=50, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'defgh', 'xyz'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc', 'F - test_draw_wrapping_text/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'de', 'F - test_draw_wrapping_text/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'fgh', 'F - test_draw_wrapping_text/screen:3')endfunction test_draw_word_wrapping_text()io.write('\ntest_draw_word_wrapping_text')App.screen.init{width=60, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def ghi', 'jkl'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.draw(Editor_state)local y = Editor_state.topApp.screen.check(y, 'abc ', 'F - test_draw_word_wrapping_text/screen:1')y = y + Editor_state.line_heightApp.screen.check(y, 'def ', 'F - test_draw_word_wrapping_text/screen:2')y = y + Editor_state.line_heightApp.screen.check(y, 'ghi', 'F - test_draw_word_wrapping_text/screen:3')endfunction test_click_on_empty_line()io.write('\ntest_click_on_empty_line')-- display two lines with the first one emptyApp.screen.init{width=50, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'', 'def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- click on the empty lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_takes_margins_into_account/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_takes_margins_into_account/cursor:pos')check_nil(Editor_state.selection1.line, 'F - test_click_takes_margins_into_account/selection is empty to avoid perturbing future edits')endfunction test_click_takes_margins_into_account()io.write('\ntest_click_takes_margins_into_account')-- display two lines with cursor on one of themApp.screen.init{width=100, height=80}Editor_state = edit.initialize_test_state()Editor_state.left = 50 -- occupy only right side of screenEditor_state.lines = load_array{'abc', 'def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- click on the other lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)-- cursor movescheck_eq(Editor_state.cursor1.line, 1, 'F - test_click_to_left_of_line/cursor:line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_click_to_left_of_line/cursor:pos')check_nil(Editor_state.selection1.line, 'F - test_click_to_left_of_line/selection is empty to avoid perturbing future edits')endfunction test_click_to_left_of_line()io.write('\ntest_click_to_left_of_line')-- display a line with the cursor in the middleApp.screen.init{width=50, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=3}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}-- click to the left of the lineedit.draw(Editor_state)edit.run_after_mouse_click(Editor_state, Editor_state.left-4,Editor_state.top+5, 1)-- cursor moves to start of lineEditor_state.selection1 = {}edit.draw(Editor_state) -- populate line_cache.starty for each line Editor_state.line_cacheedit.run_after_mouse_release(Editor_state, Editor_state.left+8,Editor_state.top+5, 1)check_eq(Editor_state.cursor1.line, 1, 'F - test_click_moves_cursor/cursor:line')check_eq(Editor_state.cursor1.pos, 2, 'F - test_click_moves_cursor/cursor:pos')-- selection is empty to avoid perturbing future editscheck_nil(Editor_state.selection1.line, 'F - test_click_moves_cursor/selection:line')check_nil(Editor_state.selection1.pos, 'F - test_click_moves_cursor/selection:pos')endEditor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}Editor_state.lines = load_array{'abc', 'def', 'xyz'}Text.redraw_all(Editor_state)function test_click_moves_cursor()io.write('\ntest_click_moves_cursor')App.screen.init{width=50, height=60}Editor_state = edit.initialize_test_state()edit.run_after_text_input(Editor_state, 'a')local y = Editor_state.topApp.screen.check(y, 'a', 'F - test_insert_first_character/screen:1')endfunction test_press_ctrl()io.write('\ntest_press_ctrl')-- press ctrl while the cursor is on textApp.screen.init{width=50, height=80}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{''}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}Editor_state.screen_top1 = {line=1, pos=1}Editor_state.screen_bottom1 = {}edit.run_after_keychord(Editor_state, 'C-m')endfunction test_move_left()io.write('\ntest_move_left')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'a'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=2}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'left')check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_left')endfunction test_move_right()io.write('\ntest_move_right')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'a'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'right')check_eq(Editor_state.cursor1.pos, 2, 'F - test_move_right')endfunction test_move_left_to_previous_line()io.write('\ntest_move_left_to_previous_line')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'left')check_eq(Editor_state.cursor1.line, 1, 'F - test_move_left_to_previous_line/line')check_eq(Editor_state.cursor1.pos, 4, 'F - test_move_left_to_previous_line/pos') -- past end of lineendfunction test_move_right_to_next_line()io.write('\ntest_move_right_to_next_line')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc', 'def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=4} -- past end of lineedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'right')check_eq(Editor_state.cursor1.line, 2, 'F - test_move_right_to_next_line/line')check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_right_to_next_line/pos')endfunction test_move_to_start_of_word()io.write('\ntest_move_to_start_of_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=3}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left')check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_to_start_of_word')endfunction test_move_to_start_of_previous_word()io.write('\ntest_move_to_start_of_previous_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=4} -- at the space between wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left')check_eq(Editor_state.cursor1.pos, 1, 'F - test_move_to_start_of_previous_word')endfunction test_skip_to_previous_word()io.write('\ntest_skip_to_previous_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=5} -- at the start of second wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left')check_eq(Editor_state.cursor1.pos, 1, 'F - test_skip_to_previous_word')endfunction test_skip_past_tab_to_previous_word()io.write('\ntest_skip_past_tab_to_previous_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def\tghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=10} -- within third wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left')check_eq(Editor_state.cursor1.pos, 9, 'F - test_skip_past_tab_to_previous_word')endfunction test_skip_multiple_spaces_to_previous_word()io.write('\ntest_skip_multiple_spaces_to_previous_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=6} -- at the start of second wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left')check_eq(Editor_state.cursor1.pos, 1, 'F - test_skip_multiple_spaces_to_previous_word')endfunction test_move_to_start_of_word_on_previous_line()io.write('\ntest_move_to_start_of_word_on_previous_line')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=2, pos=1}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-left')check_eq(Editor_state.cursor1.line, 1, 'F - test_move_to_start_of_word_on_previous_line/line')check_eq(Editor_state.cursor1.pos, 5, 'F - test_move_to_start_of_word_on_previous_line/pos')endfunction test_move_past_end_of_word()io.write('\ntest_move_past_end_of_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right')check_eq(Editor_state.cursor1.pos, 4, 'F - test_move_past_end_of_word')endfunction test_skip_to_next_word()io.write('\ntest_skip_to_next_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=4} -- at the space between wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right')check_eq(Editor_state.cursor1.pos, 8, 'F - test_skip_to_next_word')endfunction test_skip_past_tab_to_next_word()io.write('\ntest_skip_past_tab_to_next_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc\tdef'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=1} -- at the space between wordsedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right')check_eq(Editor_state.cursor1.pos, 4, 'F - test_skip_past_tab_to_next_word')endfunction test_skip_multiple_spaces_to_next_word()io.write('\ntest_skip_multiple_spaces_to_next_word')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=4} -- at the start of second wordedit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right')check_eq(Editor_state.cursor1.pos, 9, 'F - test_skip_multiple_spaces_to_next_word')endfunction test_move_past_end_of_word_on_next_line()io.write('\ntest_move_past_end_of_word_on_next_line')App.screen.init{width=120, height=60}Editor_state = edit.initialize_test_state()Editor_state.lines = load_array{'abc def', 'ghi'}Text.redraw_all(Editor_state)Editor_state.cursor1 = {line=1, pos=8}edit.draw(Editor_state)edit.run_after_keychord(Editor_state, 'M-right')check_eq(Editor_state.cursor1.line, 2, 'F - test_move_past_end_of_word_on_next_line/line')check_eq(Editor_state.cursor1.pos, 4, 'F - test_move_past_end_of_word_on_next_line/pos')end
-- Don't handle any keys here that would trigger text_input above.function Text.keychord_press(State, chord)--? print('chord', chord, State.selection1.line, State.selection1.pos)function Text.text_input(State, t)if App.mouse_down(1) then return endif App.ctrl_down() or App.alt_down() or App.cmd_down() then return endlocal before = snapshot(State, State.cursor1.line)--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos)Text.insert_at_cursor(State, t)if State.cursor_y > App.screen.height - State.line_height thenText.populate_screen_line_starting_pos(State, State.cursor1.line)Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)endrecord_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})endfunction Text.insert_at_cursor(State, t)if State.cursor1.pos thenlocal byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1.pos = State.cursor1.pos+1elseassert(State.cursor1.posB)local byte_offset = Text.offset(State.lines[State.cursor1.line].dataB, State.cursor1.posB)State.lines[State.cursor1.line].dataB = string.sub(State.lines[State.cursor1.line].dataB, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].dataB, byte_offset)Text.clear_screen_line_cache(State, State.cursor1.line)State.cursor1.posB = State.cursor1.posB+1endend
edit.mouse_release(State, x,y, mouse_button)App.screen.contents = {}edit.mouse_press(State, x,y, mouse_button)App.screen.contents = {}edit.mouse_release(State, x,y, mouse_button)App.screen.contents = {}edit.mouse_press(State, x,y, mouse_button)App.fake_mouse_release(x,y, mouse_button)edit.keychord_press(State, chord)edit.key_release(State, chord)App.screen.contents = {}-- not all keys are text_inputfunction edit.run_after_keychord(State, chord)function edit.run_after_text_input(State, t)edit.keychord_press(State, t)edit.text_input(State, t)edit.key_release(State, t)App.screen.contents = {}-- all text_input events are also keypresses-- TODO: handle chords of multiple keysfunction edit.key_release(State, key, scancode)endfunction edit.update_font_settings(State, font_height)State.font_height = font_heightText.keychord_press(State, chord)endendfunction edit.eradicate_locations_after_the_fold(State)-- eradicate side B from any locations we trackif State.cursor1.posB thenState.cursor1.posB = nilState.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data)State.cursor1.pos = Text.pos_at_start_of_screen_line(State, State.cursor1)endif State.screen_top1.posB thenState.screen_top1.posB = nilState.screen_top1.pos = utf8.len(State.lines[State.screen_top1.line].data)State.screen_top1.pos = Text.pos_at_start_of_screen_line(State, State.screen_top1)endendDrawing.keychord_press(State, chord)record_undo_event(State, {before=before, after=snapshot(State, drawing_index)})schedule_save(State)endelseif chord == 'escape' and not App.mouse_down(1) thenfor _,line in ipairs(State.lines) doif line.mode == 'drawing' thenline.show_help = falseendendelseif State.current_drawing_mode == 'name' thenif chord == 'return' thenState.current_drawing_mode = State.previous_drawing_modeState.previous_drawing_mode = nilelselocal before = snapshot(State, State.lines.current_drawing_index)local drawing = State.lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilrecord_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})elseif chord == 'backspace' thenlocal len = utf8.len(p.name)local byte_offset = Text.offset(p.name, len-1)if len == 1 then byte_offset = 0 endp.name = string.sub(p.name, 1, byte_offset)record_undo_event(State, {before=before, after=snapshot(State, State.lines.current_drawing_index)})endendschedule_save(State)function edit.keychord_press(State, chord, key)if State.selection1.line andnot State.lines.current_drawing and-- printable character created using shift key => delete selection-- (we're not creating any ctrl-shift- or alt-shift- combinations using regular/printable keys)(not App.shift_down() or utf8.len(key) == 1) andText.text_input(State, t)endfunction edit.text_input(State, t)if State.search_term thenState.search_term = State.search_term..tState.search_text = nilText.search_next(State)Drawing.mouse_release(State, x,y, mouse_button)schedule_save(State)if Drawing.before thenrecord_undo_event(State, {before=Drawing.before, after=snapshot(State, State.lines.current_drawing_index)})Drawing.before = nilendfunction edit.mouse_release(State, x,y, mouse_button)if State.search_term then return end--? print('release')if State.lines.current_drawing thenDrawing.mouse_press(State, line_index, x,y, mouse_button)breakend-- i.e. mouse_release should never look at shift stateState.old_cursor1 = State.cursor1State.old_selection1 = State.selection1State.mousepress_shift = App.shift_down()function edit.mouse_press(State, x,y, mouse_button)if State.search_term then return end
return log_browser.keychord_press(Log_browser_state, chordkey, scancode)endend-- use this sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)endreturn Text_cache[s]endreturn edit.key_release(Editor_state, key, scancode)elsefunction source.key_release(key, scancode)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'edit' thenreturn log_browser.keychord_press(Log_browser_state, chord, key)endendreturn edit.keychord_press(Editor_state, chord, key)elsekeychord_press_on_file_navigator(chord, key)returnendif chord == 'C-l' then--? print('C-l')Show_log_browser_side = not Show_log_browser_sideif Show_log_browser_side thenfunction source.keychord_press(chord, key)Cursor_time = 0 -- ensure cursor is visible immediately after it moves--? print('source keychord')if Show_file_navigator thenreturn log_browser.text_input(Log_browser_state, t)endendreturn edit.text_input(Editor_state, t)elsetext_input_on_file_navigator(t)returnendfunction source.text_input(t)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn log_browser.mouse_release(Log_browser_state, x,y, mouse_button)endendreturn edit.mouse_release(Editor_state, x,y, mouse_button)elsefunction source.mouse_release(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it movesif Focus == 'edit' thenlog_browser.mouse_press(Log_browser_state, x,y, mouse_button)for _,line_cache in ipairs(Editor_state.line_cache) do line_cache.starty = nil end -- just in case we scrollendendedit.mouse_press(Editor_state, x,y, mouse_button)elseif Show_log_browser_side and Log_browser_state.left <= x and x < Log_browser_state.right then--? print('click on log_browser side')if Focus ~= 'log_browser' thenFocus = 'log_browser'edit.mouse_press(Editor_state, x,y, mouse_button)returnendfunction source.mouse_press(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it moves--? print('mouse click', x, y)--? print(Editor_state.left, Editor_state.right)--? print(Log_browser_state.left, Log_browser_state.right)-- a copy of source.file_drop when given a filenamefunction source.switch_to_file(filename)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)endfunction source.file_drop(file)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- clear the slate for the new fileEditor_state.filename = file:getFilename()file:open('r')Editor_state.lines = load_from_file(file)file:close()Text.redraw_all(Editor_state)Editor_state.screen_top1 = {line=1, pos=1}Editor_state.cursor1 = {line=1, pos=1}
return edit.key_release(Editor_state, key, scancode)end-- use this sparinglyfunction to_text(s)if Text_cache[s] == nil thenText_cache[s] = App.newText(love.graphics.getFont(), s)endreturn Text_cache[s]endfunction run.key_release(key, scancode)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn edit.keychord_press(Editor_state, chord, key)endfunction run.keychord_press(chord, key)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn edit.text_input(Editor_state, t)endfunction run.text_input(t)Cursor_time = 0 -- ensure cursor is visible immediately after it movesreturn edit.mouse_release(Editor_state, x,y, mouse_button)endfunction run.mouse_release(x,y, mouse_button)Cursor_time = 0 -- ensure cursor is visible immediately after it movesfunction run.file_drop(file)-- first make sure to save edits on any existing fileif Editor_state.next_save thensave_to_disk(Editor_state)end-- clear the slate for the new fileApp.initialize_globals()Editor_state.filename = file:getFilename()file:open('r')Editor_state.lines = load_from_file(file)file:close()Text.redraw_all(Editor_state)love.window.setTitle('text.love - '..Editor_state.filename)endfunction run.draw()edit.draw(Editor_state)endfunction run.update(dt)Cursor_time = Cursor_time + dtedit.update(Editor_state, dt)endfunction run.quit()edit.quit(Editor_state)endfunction run.settings()
function log_browser.key_release(State, key, scancode)endfunction log_browser.keychord_press(State, chord, key)-- moveif chord == 'up' thenwhile State.screen_top1.line > 1 doState.screen_top1.line = State.screen_top1.line-1if should_show(State.lines[State.screen_top1.line]) thenbreakendendelseif chord == 'down' thenwhile State.screen_top1.line < #State.lines doState.screen_top1.line = State.screen_top1.line+1if should_show(State.lines[State.screen_top1.line]) thenbreakendendelseif chord == 'pageup' thenlocal y = 0while State.screen_top1.line > 1 and y < App.screen.height - 100 doState.screen_top1.line = State.screen_top1.line - 1if should_show(State.lines[State.screen_top1.line]) theny = y + log_browser.height(State, State.screen_top1.line)endendelseif chord == 'pagedown' thenlocal y = 0while State.screen_top1.line < #State.lines and y < App.screen.height - 100 doif should_show(State.lines[State.screen_top1.line]) theny = y + log_browser.height(State, State.screen_top1.line)endState.screen_top1.line = State.screen_top1.line + 1endendendfunction log_browser.height(State, line_index)local line = State.lines[line_index]if line.data == nil then-- section headerreturn State.line_heightelseif type(line.data) == 'string' thenreturn State.line_heightelseif line.height == nil then--? print('nil line height! rendering off screen to calculate')line.height = log_render[line.data.name](line.data, State.left, App.screen.height, State.right-State.left)endreturn line.heightendendfunction log_browser.text_input(State, t)endfunction log_browser.mouse_release(State, x,y, mouse_button)endfunction log_browser.mouse_press(State, x,y, mouse_button)local line_index = log_browser.line_index(State, x,y)if line_index == nil then-- below lower marginreturnend-- leave some space to click without focusinglocal line = State.lines[line_index]local xleft = log_browser.left_margin(State, line)local xright = log_browser.right_margin(State, line)if x < xleft or x > xright thenreturnend-- if it's a section begin/end and the section is collapsed, expand it-- TODO: how to collapse?if line.section_begin or line.section_end then-- HACK: get section reference from next/previous linelocal new_sectionif line.section_begin thenif line_index < #State.lines thenlocal next_section_stack = State.lines[line_index+1].section_stackif next_section_stack thennew_section = next_section_stack[#next_section_stack]endendelseif line.section_end thenif line_index > 1 thenlocal previous_section_stack = State.lines[line_index-1].section_stackif previous_section_stack thennew_section = previous_section_stack[#previous_section_stack]endendendif new_section and new_section.expanded == nil thennew_section.expanded = truereturnendend-- open appropriate file in source sideif line.filename ~= Editor_state.filename thensource.switch_to_file(line.filename)end-- set cursorEditor_state.cursor1 = {line=line.line_number, pos=1, posB=nil}-- make sure it's visible-- TODO: handle extremely long linesEditor_state.screen_top1.line = math.max(0, Editor_state.cursor1.line-5)-- show cursorFocus = 'edit'-- expand B sideEditor_state.expanded = trueendfunction log_browser.line_index(State, mx,my)-- duplicate some logic from log_browser.drawlocal y = State.topfor line_index = State.screen_top1.line,#State.lines dolocal line = State.lines[line_index]if should_show(line) theny = y + log_browser.height(State, line_index)if my < y thenreturn line_indexendif y > App.screen.height then break endendendend
function text_input_on_file_navigator(t)File_navigation.filter = File_navigation.filter..tFile_navigation.candidates = source.file_navigator_candidates()endfunction keychord_press_on_file_navigator(chord, key)log(2, 'file navigator: '..chord)log(2, {name='file_navigator_state', files=File_navigation.candidates, index=File_navigation.index})
if Current_app == 'run' thenif run.file_drop then run.file_drop(file) endelseif Current_app == 'source' thenif source.file_drop then source.file_drop(file) end
return edit.keychord_pressed(Editor_state, chord, key)if run.keychord_press then run.keychord_press(chord, key) endif source.keychord_press then source.keychord_press(chord, key) end
return edit.keychord_press(Editor_state, chord, key)
return edit.text_input(Editor_state, t)
return edit.key_released(Editor_state, key, scancode)if run.mouse_release then run.mouse_release(x,y, mouse_button) endif source.mouse_release then source.mouse_release(x,y, mouse_button) end
return edit.key_release(Editor_state, key, scancode)
if run.mouse_pressed then run.mouse_press(x,y, mouse_button) endif source.mouse_pressed then source.mouse_press(x,y, mouse_button) end