experiment: new edit namespace
[?]
Jul 12, 2022, 5:14 AM
2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQCDependencies
- [2]
QW5KQQTDfix a comment - [3]
Q7BDB3XQ. - [4]
SHEGBK4Hwhile we're at it, undo naming points - [5]
T7IWZFL4more precise scroll on paste - [6]
TC4HQILHwhen naming points, allow backspacing back to '' - [7]
S7ZZA3YEugh, handle absolute as well as relative paths - [8]
3EMBUWVWwhat should happen to selection while drawing? - [9]
6VXO3ZL3just keep the cursor visible after any input events - [10]
GK47BBCYstart passing left/right margins everywhere - [11]
4CXVIEBSadd args to some functions - [12]
K4OBZSHEadd args to some functions - [13]
QCQTMUZ7add args to some functions - [14]
NX3DDSCZfix a variable name - [15]
4CTZOJPCstop pretending globals are local - [16]
6LJZN727handle chords - [17]
S5VCAFKYcouple of tests for cursor down - [18]
4RUI5X52a few tests for pageup, and a bugfix - [19]
HDC3AAQPsilly reason my screenshots had an ugly black line down the left - [20]
HJ3PM2VT. - [21]
OAHNWDYG. - [22]
7SFHSB47rename - [23]
VXORMHMEdelete experimental REPL - [24]
OGUV4HSAremove some memory leaks from rendered fragments - [25]
3CS5KKCIup/down cursor movement - [26]
QU7NHFOVshow cursor - [27]
YKRF5V3Zstarting to load/save - [28]
2C7CTIQYmake space for multiple kinds of width - [29]
DLQMM265scroll past first page - [30]
TNHZZYWPupdate some documentation - [31]
KJKKASHZreduce ambitions a bit: page up/down need not start screen from the middle of a line - [32]
252M2QMDforgot to move this special case out - [33]
IZZVOCLBconfirm that we have access to all of the love API - [34]
WLHI7KD3new globals: draw partial screen line up top - [35]
NQWWTGXRswitch undo/redo to ctrl- hotkeys - [36]
TKFSYQ2Zup arrow to search previous - [37]
EFMLTMZGbugfix: restrict strokes to the drawing they started in - [38]
J2SVGR2Eexperiment: blinking cursor - [39]
RMKMPFT5fix a corner case when selecting text - [40]
3TDOZESEextract scrolling logic out of insert_return - [41]
BOFNXP5Gclicking now moves the cursor even on long, wrapped lines - [42]
YTSPVDZHfirst successful pagedown test, first bug found by test - [43]
SN2QONLIautosave on cut/paste - [44]
SR7L4QPZrevert previous commit - [45]
TVM2WIHHbugfix: autosave and undo in a couple of cases - [46]
IDG26SXKbugfix in commit e51ce12969 - [47]
DRFE3B3Zmouse buttons are integers, not strings - [48]
ZZ2B5RPQextract variables for drawing padding - [49]
MP2TBKU6bugfix: crash in Text.up() after return - [50]
4WAFGF4Zselection bugfix - [51]
AM42E4Y6avoid redundant writes on exit - [52]
MYC7XR5Qbugfix: lines that aren't drawn from the start - [53]
H2DPLWMVsnapshot: wrapping long lines at word boundaries - [54]
7IKRRESBlonger names for indices in long loops - [55]
TVCPXAAUrename - [56]
DGK5BPVIbugfix: UTF-8 in compute_fragments - [57]
AJB4LFRBtry to maintain a reasonable line width - [58]
J5IEBT64enforce press/release state only processed once - [59]
AMXTYDOFshow the line width when dragging the slider - [60]
XHLL3JQRshow cursor immediately after a mouse click - [61]
AOIRVVJArevert selection logic to before commit 3ffc2ed8f - [62]
YGCT2D2Ostart loading settings as applicable - [63]
VVXVV2D2change data model; text can now have metadata - [64]
EMRPLZPWdrop an arg from a function - [65]
WDWXNW7Vslightly strange way to move points - [66]
AVLAYODPmuch simpler - [67]
FFBIY74Nbugfix: 'escape' to cancel a stroke - [68]
RJGZD4INbinary search to most natural up/down with proportional fonts - [69]
XNFTJHC4split keyboard handling between Text and Drawing - [70]
MNWHXPBLmore lightweight; select just the stroke at the mouse - [71]
3RGHOJ25DRY some code - [72]
CVGE3SIGI feel confident now that page-down is working. - [73]
VHQCNMARseveral more modules - [74]
IRCKL6VNextract scrolling logic out of insert_at_cursor - [75]
LAW2O3NWextract variable Margin_left - [76]
K464QQR4more defensive resize handling - [77]
QCQHLMSTalways have a filename - [78]
NZ7V4BVSnote card - [79]
DHI6IJCNselecting text and deleting selections - [80]
HYEAFRZ2split mouse_pressed events between Text and Drawing - [81]
PESSMQBJno, make sure to compute line width after screen dimensions - [82]
ZUOL7X6Vmove - [83]
AMSESRTHmove some code - [84]
OIB2QPRCstart remembering where the cursor is drawn in px - [85]
D2HYRJXI. - [86]
VC2CU2GGfaster paste - [87]
FYS7TCDWbugfix - [88]
5FW7YOFThighlight selection while dragging - [89]
EMHRPJ3Rno, that's not right - [90]
AD34IX2Zcouple more tests - [91]
U7M4M2F7bugfix: don't rely on Screen_bottom1 while scrolling - [92]
VJ3ODCHRassert for a bug I saw a while ago but can no longer reproduce - [93]
M6TH7VSZrip out notion of Line_width - [94]
MGOQ5XAVstart uppercasing globals - [95]
ZNLTRNNKhighlight another global - [96]
BATTU6HWstop blanking screen while resizing - [97]
FJ4L6N74draw lines by default - [98]
G3VLJLDHuse the background color - [99]
VCMS2CWTbugfix: escape key to hide online help - [100]
QLTJG7Q3indent - [101]
BYG5CEMVsupport for naming points - [102]
WOXIYUTLbugfix: manage screen_top and cursor when resizing - [103]
JRLBUB6Lmore intuitive point delete from polygons - [104]
JCSLDGAHbeginnings of support for multiple shapes - [105]
OTIBCAUJlove2d scaffold - [106]
4NDYV4WDfix 2 bugs in line selection - [107]
XXI67EXRdrop non-existent feature from comment - [108]
AVQ5MC5Dfinish uppercasing all globals - [109]
BLWAYPKVextract a module - [110]
NQKFQSZEundo creating new drawings - [111]
2POFQQLWkeep cursor on screen when pressing 'down' - [112]
LFMI3D7Dstop scanning from start of file on every App.draw - [113]
VIU2FBNVmake sure to save right when quitting - [114]
D2GCFTTTclean up repl functionality - [115]
BTKAW76Lrename - [116]
3GFQP6IRstop saving the entire file when modifying drawings - [117]
WIDXZBNWexperiment: extremely precise scrolling on paste - [118]
4C375P53this is a bit clearer - [119]
7Q6GKOOL. - [120]
7M7LS7I2start saving some settings to disk on quit - [121]
42LVB4DEtest: naming a point - [122]
VG75U7IMbugfix: typing should delete highlighted text - [123]
G3C4FKPRbugfix: missed fixing a callsite - [124]
NP7PIUBTbugfix: restore state after C-f (find) - [125]
BZRRUIFQcorrect location of the line width slider - [126]
YIQYNVD2rip out the line-width slider - [127]
ZLJGZYQGselect text with shift + mouseclick - [128]
ESETRNLBbugfix: printing the first part of a line at the bottom made it seem non-wrapping - [129]
HOSPP2ANcrisp font rendering - [130]
CCYSVZA2bugfix: BSOD in #4. - [131]
IRV65LZPfold variables for screen dimensions into the app framework - [132]
R6GUSTBYdefault font size and line-height - [133]
YJJ4X4JGbugfix: avoid scrolling on 'end' - [134]
YPHKZVWMextract a new variable - [135]
V5TP27FPctrl-+ and ctrl-- to adjust font size - [136]
U76D4P36fix a typo - [137]
3TFEAQSWstart using some globals - [138]
3QNOKBFMbeginnings of a test harness - [139]
4VKEE43Zbugfix - [140]
PX7DDEMOautosave slightly less aggressively - [141]
PGZJ6NATensure Filename is writable when opened outside a terminal - [142]
CE4LZV4Tdrop last couple of manual tests - [143]
65XHTZEKregression: couldn't do many drawing operations because line.y was reset - [144]
DHCLUDCW. - [145]
7EQLPB3Obugfix: don't delete selection when moving cursor - [146]
OYXDYPGSget rid of debug variables - [147]
5ZFHMYQI. - [148]
2ENZW7TVselect text using mouse drag - [149]
QZH3PQFU. - [150]
Z4XRNDTRfind text - [151]
JVRL5TWLstore device-independent coordinates inside drawings - [152]
EDY3RQULgracefully handle a non-existent filename at the commandline - [153]
A2QPFRFJmove - [154]
3TTAYXPPcleanup - [155]
CIQN2MDEbugfix: typing a capital letter deletes selection - [156]
G77XIN7Mselecting a stroke - [157]
RT6EV6OPdelegate update events to drawings - [158]
T7SJSJIHtest: undo naming a point - [159]
IYW7X3WLleft/right cursor movement, deleting characters - [160]
XX7G2FFJintermingle freehand line drawings with text - [161]
2H67P75Xswitch arg for a function - [162]
B3IWYWSRdelete another arg that can be deduced - [163]
KVHUFUFVreorg - [164]
LUNH47XXmake text and drawings the same width - [165]
Y6FTGOHJsimpler - [166]
73OCE2MCafter much struggle, a brute-force undo - [167]
5DOTWNVMright margin - [168]
CTJ3IZGSadd args to some functions - [169]
BULPIBEGbeginnings of a module for the text editor - [170]
AVTNUQYRbasic test-enabled framework - [171]
7RN3AETYbugfix: text sometimes getting colored like drawing borders - [172]
2INHXC3Kposition cursor by clicking on text - [173]
JFFUF5ALoverride mouse state lookups in tests - [174]
2RXZ3PGObeginning of a new approach to scroll+wrap - [175]
6PUNJS5Bbackspace - [176]
6J3NXBYGaffordance to adjust width for word wrap - [177]
242L3OQXbugfix: ensure Cursor_line is always on a text line - [178]
HRWN5V6JDevine's suggestion to try to live with just freehand - [179]
6DE7RBZ6move mouse_released events to Drawing - [180]
UWNHC4AAredo y computations - [181]
KMRJOSLYbugfix: delete selection before pasting - [182]
AQQQNDTLyet another bugfix in selection management - [183]
LS55YKGWswitch copy/paste to ctrl- hotkeys - [184]
DAENUOGVeliminate assumptions that line length == size in bytes - [*]
R5QXEHUIsomebody stop me - [*]
BJ5X5O4Alet's prevent the text cursor from ever getting on a drawing
Change contents
- edit in main.lua at line 5
- edit in main.lua at line 6[16.21]→[16.3:18](∅→∅),[16.18]→[16.2:19](∅→∅),[16.21]→[16.2:19](∅→∅),[16.19]→[15.31:79](∅→∅),[15.79]→[16.47:78](∅→∅),[16.47]→[16.47:78](∅→∅)
require 'file'require 'button'require 'text'require 'drawing'require 'geom'require 'help'require 'icons' - edit in main.lua at line 8
-- delegate most business logic to a layer that can be reused by other projectsrequire 'edit' - replacement in main.lua at line 14[16.914]→[16.1:66](∅→∅),[16.1566]→[16.1:66](∅→∅),[16.22]→[16.1:66](∅→∅),[16.66]→[16.4:44](∅→∅),[16.44]→[16.1880:2049](∅→∅),[16.2049]→[16.105:375](∅→∅),[16.105]→[16.105:375](∅→∅),[16.104]→[16.104:134](∅→∅),[16.375]→[16.104:134](∅→∅),[16.1787]→[16.104:134](∅→∅),[16.104]→[16.104:134](∅→∅),[16.134]→[16.105:189](∅→∅),[16.189]→[16.163:500](∅→∅),[16.163]→[16.163:500](∅→∅),[16.547]→[2.2:101](∅→∅),[2.101]→[16.643:878](∅→∅),[16.643]→[16.643:878](∅→∅),[16.1237]→[16.2:35](∅→∅),[16.35]→[16.8451:8975](∅→∅),[16.8975]→[16.1167:1273](∅→∅),[16.1273]→[16.9024:9108](∅→∅),[16.9024]→[16.9024:9108](∅→∅),[16.9108]→[3.4:53](∅→∅),[3.53]→[16.9108:9198](∅→∅),[16.9108]→[16.9108:9198](∅→∅),[16.9198]→[16.10923:10940](∅→∅),[16.10940]→[16.1217:1343](∅→∅),[16.1343]→[16.1391:1485](∅→∅),[16.1718]→[16.1391:1485](∅→∅),[16.10940]→[16.1391:1485](∅→∅),[16.204]→[16.54:95](∅→∅),[16.1206]→[16.54:95](∅→∅),[16.1485]→[16.54:95](∅→∅),[16.4217]→[16.54:95](∅→∅),[16.10940]→[16.54:95](∅→∅),[16.114]→[16.54:95](∅→∅),[16.95]→[16.3:4](∅→∅),[16.1241]→[16.79:137](∅→∅),[16.86]→[16.1:2](∅→∅),[16.255]→[16.1:2](∅→∅),[16.1318]→[16.1:2](∅→∅),[16.4239]→[16.1:2](∅→∅),[16.313]→[16.1:2](∅→∅),[16.2]→[16.5:25](∅→∅),[16.25]→[16.4127:4161](∅→∅),[16.2]→[16.4127:4161](∅→∅),[16.4161]→[16.7:89](∅→∅),[16.89]→[16.26:27](∅→∅),[16.4161]→[16.26:27](∅→∅),[16.27]→[16.1462:1478](∅→∅),[16.4161]→[16.1462:1478](∅→∅),[16.1478]→[16.2595:2612](∅→∅),[16.2612]→[16.4602:4662](∅→∅),[16.4662]→[16.329:453](∅→∅),[16.2612]→[16.329:453](∅→∅),[16.13]→[16.3:4](∅→∅),[16.341]→[16.3:4](∅→∅),[16.453]→[16.3:4](∅→∅),[16.1478]→[16.3:4](∅→∅),[16.2612]→[16.3:4](∅→∅),[16.4161]→[16.3:4](∅→∅),[16.21]→[16.3:4](∅→∅),[16.4]→[16.3:63](∅→∅),[16.63]→[16.137:153](∅→∅),[16.153]→[16.1567:1568](∅→∅),[16.63]→[16.1567:1568](∅→∅),[16.1568]→[16.8125:8163](∅→∅),[16.8163]→[16.2543:2590](∅→∅),[16.2590]→[16.391:455](∅→∅)
-- a line is either text or a drawing-- a text is a table with:-- mode = 'text',-- string data,-- startpos, the index of data the line starts rendering from (if currently on screen), can only be >1 for topmost line on screen-- starty, the y coord in pixels-- some cached data that's blown away and recomputed when data changes:-- fragments: snippets of rendered love.graphics.Text, guaranteed to not wrap-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line-- a drawing is a table with:-- mode = 'drawing'-- a (y) coord in pixels (updated while painting screen),-- a (h)eight,-- an array of points, and-- an array of shapes-- a shape is a table containing:-- a mode-- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)-- an array vertices for mode 'polygon', 'rectangle', 'square'-- p1, p2 for mode 'line'-- center, radius for mode 'circle'-- center, radius, start_angle, end_angle for mode 'arc'-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide-- The field names are carefully chosen so that switching modes in midstream-- remembers previously entered points where that makes sense.Lines = {{mode='text', data=''}}-- Lines can be too long to fit on screen, in which case they _wrap_ into-- multiple _screen lines_.---- Therefore, any potential location for the cursor can be described in two ways:-- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units)-- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line.---- Most of the time we'll only persist positions in schema 1, translating to-- schema 2 when that's convenient.---- Make sure these coordinates are never aliased, so that changing one causes-- action at a distance.Screen_top1 = {line=1, pos=1} -- position of start of screen line at top of screenCursor1 = {line=1, pos=1} -- position of cursorScreen_bottom1 = {line=1, pos=1} -- position of start of screen line at bottom of screenSelection1 = {}Old_cursor1, Old_selection1, Mousepress_shift = nil -- some extra state to compute selection between mouse press and releaseRecent_mouse = {} -- when selecting text, avoid recomputing some state on every single frameCursor_x, Cursor_y = 0, 0 -- in pixelsCurrent_drawing_mode = 'line'Previous_drawing_mode = nil-- values for testsFont_height = 14Line_height = 15-- widest possible character widthEm = App.newText(love.graphics.getFont(), 'm')Margin_top = 15Margin_left = 25Margin_right = 25Margin_width = Margin_left + Margin_rightDrawing_padding_top = 10Drawing_padding_bottom = 10Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottomFilename = love.filesystem.getUserDirectory()..'/lines.txt'Next_save = nil-- undoHistory = {}Next_history = 1-- searchSearch_term = nilSearch_text = nilSearch_backup = nil -- stuff to restore when cancelling searchreturn edit.initialize_globals()end - edit in main.lua at line 17[16.163]→[16.163:196](∅→∅),[16.196]→[16.204:240](∅→∅),[16.55]→[16.8163:8164](∅→∅),[16.196]→[16.8163:8164](∅→∅),[16.240]→[16.8163:8164](∅→∅),[16.455]→[16.8163:8164](∅→∅),[16.2017]→[16.8163:8164](∅→∅),[16.2680]→[16.8163:8164](∅→∅),[16.8163]→[16.8163:8164](∅→∅),[16.8164]→[16.1568:1600](∅→∅),[16.1568]→[16.1568:1600](∅→∅)
-- resizeLast_resize_time = nil-- blinking cursorCursor_time = 0end -- App.initialize_globals - replacement in main.lua at line 139[16.1373]→[16.3:26](∅→∅),[16.209]→[16.3:26](∅→∅),[16.336]→[16.260:261](∅→∅),[16.261]→[16.658:692](∅→∅),[16.692]→[16.1144:1218](∅→∅),[16.261]→[16.1144:1218](∅→∅),[16.274]→[16.5:46](∅→∅),[16.448]→[16.5:46](∅→∅),[16.1218]→[16.5:46](∅→∅),[16.545]→[16.5:46](∅→∅),[16.46]→[5.8:24](∅→∅),[5.24]→[16.1479:1502](∅→∅),[16.46]→[16.1479:1502](∅→∅),[16.1502]→[16.58:81](∅→∅),[16.18]→[16.58:81](∅→∅),[16.81]→[16.8:89](∅→∅),[16.89]→[16.82:126](∅→∅),[16.44]→[16.82:126](∅→∅),[16.126]→[16.4162:4220](∅→∅),[16.4220]→[16.90:679](∅→∅),[16.679]→[16.306:331](∅→∅),[16.394]→[16.306:331](∅→∅),[16.331]→[16.680:976](∅→∅),[16.976]→[16.2189:2201](∅→∅),[16.2189]→[16.2189:2201](∅→∅),[16.2201]→[16.977:1371](∅→∅),[16.108]→[16.1574:1584](∅→∅),[16.183]→[16.1574:1584](∅→∅),[16.264]→[16.1574:1584](∅→∅),[16.328]→[16.1574:1584](∅→∅),[16.1371]→[16.1574:1584](∅→∅),[16.2159]→[16.1574:1584](∅→∅),[16.2447]→[16.1574:1584](∅→∅),[16.1574]→[16.1574:1584](∅→∅),[16.1584]→[16.1372:1416](∅→∅),[16.1416]→[10.576:691](∅→∅),[10.691]→[16.1474:1527](∅→∅),[16.1474]→[16.1474:1527](∅→∅),[16.421]→[16.1294:1302](∅→∅),[16.1527]→[16.1294:1302](∅→∅),[16.1577]→[16.1294:1302](∅→∅),[16.1584]→[16.1294:1302](∅→∅),[16.1294]→[16.1294:1302](∅→∅),[16.1302]→[5.25:89](∅→∅),[5.89]→[16.410:416](∅→∅),[16.119]→[16.410:416](∅→∅),[16.1302]→[16.410:416](∅→∅),[16.410]→[16.410:416](∅→∅),[16.2178]→[16.408:520](∅→∅),[16.2469]→[16.408:520](∅→∅),[16.416]→[16.408:520](∅→∅),[16.520]→[16.2820:2875](∅→∅)
Button_handlers = {}love.graphics.setColor(0, 0, 0)--? print(Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos)assert(Text.le1(Screen_top1, Cursor1))Cursor_y = -1local y = Margin_top--? print('== draw')for line_index = Screen_top1.line,#Lines dolocal line = Lines[line_index]--? print('draw:', y, line_index, line)if y + Line_height > App.screen.height then break endScreen_bottom1.line = line_indexif line.mode == 'text' and line.data == '' thenline.starty = yline.startpos = 1-- insert new drawingbutton('draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},icon = icon.insert_drawing,onpress1 = function()Drawing.before = snapshot(line_index-1, line_index)table.insert(Lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})if Cursor1.line >= line_index thenCursor1.line = Cursor1.line+1endschedule_save()record_undo_event({before=Drawing.before, after=snapshot(line_index-1, line_index+1)})end})if Search_term == nil thenif line_index == Cursor1.line thenText.draw_cursor(Margin_left, y)endendScreen_bottom1.pos = Screen_top1.posy = y + Line_heightelseif line.mode == 'drawing' theny = y+Drawing_padding_topline.y = yDrawing.draw(line)y = y + Drawing.pixels(line.h) + Drawing_padding_bottomelseline.starty = yline.startpos = 1if line_index == Screen_top1.line thenline.startpos = Screen_top1.posend--? print('text.draw', y, line_index)y, Screen_bottom1.pos = Text.draw(line, line_index, line.starty, Margin_left, App.screen.width-Margin_right)y = y + Line_height--? print('=> y', y)endendif Cursor_y == -1 thenCursor_y = App.screen.heightend--? print('screen bottom: '..tostring(Screen_bottom1.pos)..' in '..tostring(Lines[Screen_bottom1.line].data))if Search_term thenText.draw_search_bar()endedit.draw() - replacement in main.lua at line 143[16.1398]→[16.241:274](∅→∅),[16.274]→[16.449:512](∅→∅),[16.1398]→[16.449:512](∅→∅),[16.512]→[16.282:333](∅→∅),[16.333]→[16.570:621](∅→∅),[16.570]→[16.570:621](∅→∅),[16.706]→[16.621:635](∅→∅),[16.621]→[16.621:635](∅→∅),[16.635]→[16.3:24](∅→∅),[16.1398]→[16.3:24](∅→∅),[16.446]→[16.3:24](∅→∅),[16.24]→[16.334:444](∅→∅)
Cursor_time = Cursor_time + dt-- some hysteresis while resizingif Last_resize_time thenif App.getTime() - Last_resize_time < 0.1 thenreturnelseLast_resize_time = nilendendDrawing.update(dt)if Next_save and Next_save < App.getTime() thensave_to_disk(Lines, Filename)Next_save = nilendedit.update(dt) - edit in main.lua at line 146
function schedule_save()if Next_save == nil thenNext_save = App.getTime() + 3 -- short enough that you're likely to still remember what you didendend - replacement in main.lua at line 147
-- make sure to save before quittingif Next_save thensave_to_disk(Lines, Filename)endedit.quit() - replacement in main.lua at line 164[16.1444]→[16.2876:2909](∅→∅),[16.2909]→[9.8:83](∅→∅),[9.83]→[16.8:62](∅→∅),[16.2909]→[16.8:62](∅→∅),[16.62]→[16.49:99](∅→∅),[16.1402]→[16.49:99](∅→∅),[16.1444]→[16.49:99](∅→∅),[16.2909]→[16.49:99](∅→∅),[16.49]→[16.49:99](∅→∅),[16.99]→[16.1:2](∅→∅),[16.47]→[16.1:2](∅→∅),[16.877]→[16.304:346](∅→∅),[16.2]→[16.304:346](∅→∅),[16.346]→[16.176:208](∅→∅),[16.536]→[16.176:208](∅→∅),[16.176]→[16.176:208](∅→∅),[16.208]→[16.143:225](∅→∅),[16.119]→[16.119:218](∅→∅),[16.225]→[16.119:218](∅→∅),[16.156]→[16.119:218](∅→∅),[16.218]→[16.1811:2346](∅→∅),[16.1811]→[16.1811:2346](∅→∅),[16.2346]→[16.153:205](∅→∅),[16.205]→[12.233:326](∅→∅),[12.326]→[16.254:264](∅→∅),[16.254]→[16.254:264](∅→∅),[16.264]→[16.63:141](∅→∅),[16.2422]→[16.63:141](∅→∅),[16.141]→[16.361:410](∅→∅),[16.292]→[16.361:410](∅→∅),[16.538]→[16.361:410](∅→∅),[16.769]→[16.361:410](∅→∅),[16.1528]→[16.361:410](∅→∅),[16.1640]→[16.361:410](∅→∅),[16.1837]→[16.361:410](∅→∅),[16.2015]→[16.361:410](∅→∅),[16.2422]→[16.361:410](∅→∅),[16.361]→[16.361:410](∅→∅),[16.410]→[16.539:584](∅→∅),[16.584]→[16.41:173](∅→∅),[16.173]→[14.8:63](∅→∅),[14.63]→[16.142:156](∅→∅),[16.633]→[16.142:156](∅→∅),[16.156]→[16.999:1023](∅→∅),[16.569]→[16.999:1023](∅→∅),[16.633]→[16.999:1023](∅→∅),[16.999]→[16.999:1023](∅→∅)
if Search_term then return end-- ensure cursor is visible immediately after it movesCursor_time = 0--? print('press', Selection1.line, Selection1.pos)propagate_to_button_handlers(x,y, mouse_button)for line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y, Margin_left, App.screen.width-Margin_right) then-- delicate dance between cursor, selection and old cursor/selection-- scenarios:-- regular press+release: sets cursor, clears selection-- shift press+release:-- sets selection to old cursor if not set otherwise leaves it untouched-- sets cursor-- press and hold to start a selection: sets selection on press, cursor on release-- press and hold, then press shift: ignore shift-- i.e. mousereleased should never look at shift stateOld_cursor1 = Cursor1Old_selection1 = Selection1Mousepress_shift = App.shift_down()Selection1 = {line=line_index,pos=Text.to_pos_on_line(line, x, y, Margin_left, App.screen.width-Margin_right),}--? print('selection', Selection1.line, Selection1.pos)breakendelseif line.mode == 'drawing' thenif Drawing.in_drawing(line, x, y) thenLines.current_drawing_index = line_indexLines.current_drawing = lineDrawing.before = snapshot(line_index)Drawing.mouse_pressed(line, x,y, mouse_button)breakendendendreturn edit.mouse_pressed(x,y, mouse_button) - replacement in main.lua at line 168[14.110]→[16.2910:2943](∅→∅),[16.1485]→[16.2910:2943](∅→∅),[16.2943]→[16.1403:1426](∅→∅),[16.1426]→[9.84:159](∅→∅),[9.159]→[16.293:325](∅→∅),[16.1426]→[16.293:325](∅→∅),[16.2943]→[16.293:325](∅→∅),[16.325]→[14.111:157](∅→∅),[14.157]→[16.610:630](∅→∅),[16.365]→[16.610:630](∅→∅),[16.630]→[16.208:364](∅→∅),[16.208]→[16.208:364](∅→∅),[16.364]→[16.365:450](∅→∅),[16.365]→[16.365:450](∅→∅),[16.450]→[16.226:310](∅→∅),[16.160]→[16.770:809](∅→∅),[16.310]→[16.770:809](∅→∅),[16.208]→[16.770:809](∅→∅),[16.809]→[16.265:318](∅→∅),[16.318]→[12.327:422](∅→∅),[12.422]→[16.369:381](∅→∅),[16.369]→[16.369:381](∅→∅),[16.381]→[16.157:214](∅→∅),[16.157]→[16.157:214](∅→∅),[16.84]→[16.2423:2619](∅→∅),[16.214]→[16.2423:2619](∅→∅),[16.2140]→[16.2423:2619](∅→∅),[16.157]→[16.2423:2619](∅→∅),[16.2619]→[16.617:631](∅→∅),[16.617]→[16.617:631](∅→∅),[16.631]→[16.2620:2682](∅→∅),[16.2682]→[16.219:303](∅→∅),[16.303]→[16.215:231](∅→∅),[16.2682]→[16.215:231](∅→∅),[16.231]→[16.631:661](∅→∅),[16.2682]→[16.631:661](∅→∅),[16.631]→[16.631:661](∅→∅),[16.661]→[16.232:293](∅→∅),[16.293]→[16.661:667](∅→∅),[16.1661]→[16.661:667](∅→∅),[16.661]→[16.661:667](∅→∅)
if Search_term then return end--? print('release')-- ensure cursor is visible immediately after it movesCursor_time = 0if Lines.current_drawing thenDrawing.mouse_released(x,y, mouse_button)schedule_save()if Drawing.before thenrecord_undo_event({before=Drawing.before, after=snapshot(Lines.current_drawing_index)})Drawing.before = nilendelsefor line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y, Margin_left, App.screen.width-Margin_right) then--? print('reset selection')Cursor1 = {line=line_index,pos=Text.to_pos_on_line(line, x, y, Margin_left, App.screen.width-Margin_right),}--? print('cursor', Cursor1.line, Cursor1.pos)if Mousepress_shift thenif Old_selection1.line == nil thenSelection1 = Old_cursor1elseSelection1 = Old_selection1endendOld_cursor1, Old_selection1, Mousepress_shift = nilif eq(Cursor1, Selection1) thenSelection1 = {}endbreakendendend--? print('selection:', Selection1.line, Selection1.pos)endreturn edit.mouse_released(x,y, mouse_button) - replacement in main.lua at line 172[16.1512]→[9.160:235](∅→∅),[9.235]→[16.1668:1745](∅→∅),[16.1512]→[16.1668:1745](∅→∅),[16.1745]→[16.2944:3089](∅→∅),[16.1512]→[16.2944:3089](∅→∅),[16.3089]→[4.9:66](∅→∅),[4.66]→[16.5:47](∅→∅),[16.3089]→[16.5:47](∅→∅),[16.47]→[16.237:319](∅→∅),[16.237]→[16.237:319](∅→∅),[16.319]→[4.67:151](∅→∅),[4.151]→[16.319:354](∅→∅),[16.319]→[16.319:354](∅→∅),[16.354]→[16.631:649](∅→∅)
-- ensure cursor is visible immediately after it movesCursor_time = 0for _,line in ipairs(Lines) do line.y = nil end -- just in case we scrollif Search_term thenSearch_term = Search_term..tSearch_text = nilText.search_next()elseif Current_drawing_mode == 'name' thenlocal before = snapshot(Lines.current_drawing_index)local drawing = Lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]p.name = p.name..trecord_undo_event({before=before, after=snapshot(Lines.current_drawing_index)})elseText.textinput(t)endschedule_save()return edit.textinput(t) - replacement in main.lua at line 176[16.836]→[9.236:311](∅→∅),[9.311]→[16.836:861](∅→∅),[16.836]→[16.836:861](∅→∅),[16.861]→[8.8:44](∅→∅),[8.44]→[16.861:1225](∅→∅),[16.861]→[16.861:1225](∅→∅),[16.1225]→[11.741:811](∅→∅),[11.811]→[16.1316:1322](∅→∅),[16.1316]→[16.1316:1322](∅→∅),[16.1322]→[16.3090:3190](∅→∅),[16.1823]→[16.3090:3190](∅→∅),[16.1550]→[16.3090:3190](∅→∅),[16.3190]→[16.456:564](∅→∅),[16.564]→[16.17591:17683](∅→∅),[16.564]→[16.3262:3344](∅→∅),[16.17683]→[16.3262:3344](∅→∅),[16.3262]→[16.3262:3344](∅→∅),[16.3344]→[16.565:591](∅→∅),[16.591]→[16.3344:3421](∅→∅),[16.3344]→[16.3344:3421](∅→∅),[16.3421]→[16.1543:1599](∅→∅),[16.1599]→[16.3477:3654](∅→∅),[16.3477]→[16.3477:3654](∅→∅),[16.3654]→[16.1244:1303](∅→∅),[16.1303]→[16.3654:3723](∅→∅),[16.3654]→[16.3654:3723](∅→∅),[16.3723]→[16.592:715](∅→∅),[16.715]→[16.3788:3819](∅→∅),[16.3788]→[16.3788:3819](∅→∅),[16.961]→[16.4279:4308](∅→∅),[16.3819]→[16.4279:4308](∅→∅),[16.4308]→[16.445:489](∅→∅),[16.174]→[16.4448:4470](∅→∅),[16.489]→[16.4448:4470](∅→∅),[16.4448]→[16.4448:4470](∅→∅),[16.982]→[16.4470:4499](∅→∅),[16.4470]→[16.4470:4499](∅→∅),[16.4499]→[16.490:534](∅→∅),[16.534]→[16.4593:4615](∅→∅),[16.4593]→[16.4593:4615](∅→∅),[16.221]→[16.4661:4690](∅→∅),[16.1003]→[16.4661:4690](∅→∅),[16.4661]→[16.4661:4690](∅→∅),[16.4690]→[16.535:568](∅→∅),[16.568]→[16.4773:4795](∅→∅),[16.4773]→[16.4773:4795](∅→∅),[16.1024]→[16.71:100](∅→∅),[16.265]→[16.71:100](∅→∅),[16.100]→[16.26:105](∅→∅),[16.105]→[16.100:351](∅→∅),[16.100]→[16.100:351](∅→∅),[16.351]→[16.17684:17776](∅→∅),[16.17776]→[16.650:672](∅→∅),[16.43]→[16.351:359](∅→∅),[16.672]→[16.351:359](∅→∅),[16.17776]→[16.351:359](∅→∅),[16.351]→[16.351:359](∅→∅),[16.1045]→[16.359:388](∅→∅),[16.359]→[16.359:388](∅→∅),[16.388]→[16.106:185](∅→∅),[16.185]→[16.388:638](∅→∅),[16.388]→[16.388:638](∅→∅),[16.638]→[16.17777:17869](∅→∅),[16.17869]→[16.673:695](∅→∅),[16.80]→[16.38:46](∅→∅),[16.695]→[16.38:46](∅→∅),[16.17869]→[16.38:46](∅→∅),[16.638]→[16.38:46](∅→∅),[16.1066]→[16.46:90](∅→∅),[16.46]→[16.46:90](∅→∅),[16.90]→[16.186:265](∅→∅),[16.265]→[16.90:173](∅→∅),[16.90]→[16.90:173](∅→∅),[16.1087]→[16.173:202](∅→∅),[16.173]→[16.173:202](∅→∅),[16.202]→[16.266:345](∅→∅),[16.345]→[11.812:889](∅→∅),[11.889]→[16.237:289](∅→∅),[16.237]→[16.237:289](∅→∅),[16.289]→[16.696:716](∅→∅),[16.42]→[16.289:318](∅→∅),[16.716]→[16.289:318](∅→∅),[16.1108]→[16.289:318](∅→∅),[16.289]→[16.289:318](∅→∅),[16.318]→[16.346:425](∅→∅),[16.425]→[16.318:582](∅→∅),[16.318]→[16.318:582](∅→∅),[16.80]→[16.620:753](∅→∅),[16.125]→[16.620:753](∅→∅),[16.620]→[16.620:753](∅→∅),[16.791]→[16.791:802](∅→∅),[16.942]→[16.8:41](∅→∅),[16.802]→[16.8:41](∅→∅),[16.41]→[16.849:859](∅→∅),[16.1256]→[16.849:859](∅→∅),[16.849]→[16.849:859](∅→∅),[16.859]→[16.126:134](∅→∅),[16.134]→[16.593:638](∅→∅),[16.638]→[13.664:752](∅→∅),[16.328]→[16.638:646](∅→∅),[16.434]→[16.638:646](∅→∅),[13.752]→[16.638:646](∅→∅),[16.1876]→[16.638:646](∅→∅),[16.638]→[16.638:646](∅→∅),[16.646]→[16.717:737](∅→∅),[16.77]→[16.1877:1959](∅→∅),[16.737]→[16.1877:1959](∅→∅),[16.646]→[16.1877:1959](∅→∅),[16.1129]→[16.1959:1992](∅→∅),[16.1959]→[16.1959:1992](∅→∅),[16.1992]→[16.1344:1402](∅→∅),[16.1402]→[16.426:457](∅→∅),[16.1407]→[16.426:457](∅→∅),[16.3882]→[16.426:457](∅→∅),[16.457]→[16.8:69](∅→∅),[16.69]→[16.8:189](∅→∅),[16.189]→[16.738:760](∅→∅),[16.760]→[16.225:233](∅→∅),[16.225]→[16.225:233](∅→∅),[16.11]→[16.1458:1516](∅→∅),[16.1516]→[16.74:187](∅→∅),[16.1525]→[16.74:187](∅→∅),[16.74]→[16.74:187](∅→∅),[16.187]→[16.1585:1593](∅→∅),[16.155]→[16.1585:1593](∅→∅),[16.1593]→[16.387:556](∅→∅),[16.556]→[16.218:277](∅→∅),[16.277]→[16.48:92](∅→∅),[16.556]→[16.48:92](∅→∅),[16.92]→[16.592:706](∅→∅),[16.592]→[16.592:706](∅→∅),[16.706]→[16.532:620](∅→∅),[16.620]→[16.706:782](∅→∅),[16.706]→[16.706:782](∅→∅),[16.782]→[16.1600:1655](∅→∅),[16.1655]→[6.8:53](∅→∅),[6.53]→[16.837:889](∅→∅),[16.1655]→[16.837:889](∅→∅),[16.837]→[16.837:889](∅→∅),[16.889]→[16.621:709](∅→∅),[16.709]→[16.889:899](∅→∅),[16.889]→[16.889:899](∅→∅),[16.364]→[16.899:907](∅→∅),[16.899]→[16.899:907](∅→∅),[16.907]→[16.761:781](∅→∅),[16.163]→[16.5015:5022](∅→∅),[16.781]→[16.5015:5022](∅→∅),[16.1180]→[16.5015:5022](∅→∅),[16.2194]→[16.5015:5022](∅→∅),[16.4892]→[16.5015:5022](∅→∅),[16.5015]→[16.5015:5022](∅→∅),[16.5022]→[16.458:537](∅→∅),[16.537]→[16.5022:5055](∅→∅),[16.5022]→[16.5022:5055](∅→∅),[16.293]→[16.350:356](∅→∅),[16.925]→[16.350:356](∅→∅),[16.5055]→[16.350:356](∅→∅),[16.350]→[16.350:356](∅→∅)
-- ensure cursor is visible immediately after it movesCursor_time = 0if Selection1.line andnot 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) andchord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and backspace ~= 'delete' and not App.is_cursor_movement(chord) thenText.delete_selection(Margin_left, App.screen.width-Margin_right)endif Search_term thenif chord == 'escape' thenSearch_term = nilSearch_text = nilCursor1 = Search_backup.cursorScreen_top1 = Search_backup.screen_topSearch_backup = nilText.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leakselseif chord == 'return' thenSearch_term = nilSearch_text = nilSearch_backup = nilelseif chord == 'backspace' thenlocal len = utf8.len(Search_term)local byte_offset = Text.offset(Search_term, len)Search_term = string.sub(Search_term, 1, byte_offset-1)Search_text = nilelseif chord == 'down' thenCursor1.pos = Cursor1.pos+1Text.search_next()elseif chord == 'up' thenText.search_previous()endreturnelseif chord == 'C-f' thenSearch_term = ''Search_backup = {cursor={line=Cursor1.line, pos=Cursor1.pos}, screen_top={line=Screen_top1.line, pos=Screen_top1.pos}}assert(Search_text == nil)elseif chord == 'C-=' theninitialize_font_settings(Font_height+2)Text.redraw_all()elseif chord == 'C--' theninitialize_font_settings(Font_height-2)Text.redraw_all()elseif chord == 'C-0' theninitialize_font_settings(20)Text.redraw_all()elseif chord == 'C-z' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal event = undo_event()if event thenlocal src = event.beforeScreen_top1 = deepcopy(src.screen_top)Cursor1 = deepcopy(src.cursor)Selection1 = deepcopy(src.selection)patch(Lines, event.after, event.before)Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaksschedule_save()endelseif chord == 'C-y' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal event = redo_event()if event thenlocal src = event.afterScreen_top1 = deepcopy(src.screen_top)Cursor1 = deepcopy(src.cursor)Selection1 = deepcopy(src.selection)patch(Lines, event.before, event.after)Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaksschedule_save()end-- clipboardelseif chord == 'C-c' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal s = Text.selection()if s thenApp.setClipboardText(s)endelseif chord == 'C-x' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal s = Text.cut_selection(Margin_left, App.screen.width-Margin_right)if s thenApp.setClipboardText(s)endschedule_save()elseif chord == 'C-v' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scroll-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.local before_line = Cursor1.linelocal before = snapshot(before_line)local clipboard_data = App.getClipboardText()for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenText.insert_return()elseText.insert_at_cursor(c)endendif Text.cursor_past_screen_bottom() thenText.snap_cursor_to_bottom_of_screen(Margin_left, App.screen.height-Margin_right)endschedule_save()record_undo_event({before=before, after=snapshot(before_line, Cursor1.line)})-- dispatch to drawing or textelseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then-- DON'T reset line.y herelocal drawing_index, drawing = Drawing.current_drawing()if drawing_index thenlocal before = snapshot(drawing_index)Drawing.keychord_pressed(chord)record_undo_event({before=before, after=snapshot(drawing_index)})schedule_save()endelseif chord == 'escape' and not App.mouse_down(1) thenfor _,line in ipairs(Lines) doif line.mode == 'drawing' thenline.show_help = falseendendelseif Current_drawing_mode == 'name' thenif chord == 'return' thenCurrent_drawing_mode = Previous_drawing_modePrevious_drawing_mode = nilelselocal before = snapshot(Lines.current_drawing_index)local drawing = Lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilrecord_undo_event({before=before, after=snapshot(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({before=before, after=snapshot(Lines.current_drawing_index)})endendschedule_save()elsefor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrollText.keychord_pressed(chord)endreturn edit.keychord_pressed(chord, key) - edit in main.lua at line 180
return edit.key_released(key, scancode) - file addition: edit.lua[186.2]
utf8 = require 'utf8'require 'file'require 'button'require 'text'require 'drawing'require 'geom'require 'help'require 'icons'edit = {}-- run in both tests and a real runfunction edit.initialize_globals()-- a line is either text or a drawing-- a text is a table with:-- mode = 'text',-- string data,-- startpos, the index of data the line starts rendering from (if currently on screen), can only be >1 for topmost line on screen-- starty, the y coord in pixels-- some cached data that's blown away and recomputed when data changes:-- fragments: snippets of rendered love.graphics.Text, guaranteed to not wrap-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line-- a drawing is a table with:-- mode = 'drawing'-- a (y) coord in pixels (updated while painting screen),-- a (h)eight,-- an array of points, and-- an array of shapes-- a shape is a table containing:-- a mode-- an array points for mode 'freehand' (raw x,y coords; freehand drawings don't pollute the points array of a drawing)-- an array vertices for mode 'polygon', 'rectangle', 'square'-- p1, p2 for mode 'line'-- center, radius for mode 'circle'-- center, radius, start_angle, end_angle for mode 'arc'-- Unless otherwise specified, coord fields are normalized; a drawing is always 256 units wide-- The field names are carefully chosen so that switching modes in midstream-- remembers previously entered points where that makes sense.Lines = {{mode='text', data=''}}-- Lines can be too long to fit on screen, in which case they _wrap_ into-- multiple _screen lines_.---- Therefore, any potential location for the cursor can be described in two ways:-- * schema 1: As a combination of line index and position within a line (in utf8 codepoint units)-- * schema 2: As a combination of line index, screen line index within the line, and a position within the screen line.---- Most of the time we'll only persist positions in schema 1, translating to-- schema 2 when that's convenient.---- Make sure these coordinates are never aliased, so that changing one causes-- action at a distance.Screen_top1 = {line=1, pos=1} -- position of start of screen line at top of screenCursor1 = {line=1, pos=1} -- position of cursorScreen_bottom1 = {line=1, pos=1} -- position of start of screen line at bottom of screenSelection1 = {}Old_cursor1, Old_selection1, Mousepress_shift = nil -- some extra state to compute selection between mouse press and releaseRecent_mouse = {} -- when selecting text, avoid recomputing some state on every single frameCursor_x, Cursor_y = 0, 0 -- in pixelsCurrent_drawing_mode = 'line'Previous_drawing_mode = nil-- values for testsFont_height = 14Line_height = 15-- widest possible character widthEm = App.newText(love.graphics.getFont(), 'm')Margin_top = 15Margin_left = 25Margin_right = 25Margin_width = Margin_left + Margin_rightDrawing_padding_top = 10Drawing_padding_bottom = 10Drawing_padding_height = Drawing_padding_top + Drawing_padding_bottomFilename = love.filesystem.getUserDirectory()..'/lines.txt'Next_save = nil-- undoHistory = {}Next_history = 1-- searchSearch_term = nilSearch_text = nilSearch_backup = nil -- stuff to restore when cancelling search-- resizeLast_resize_time = nil-- blinking cursorCursor_time = 0end -- App.initialize_globalsfunction edit.draw()Button_handlers = {}love.graphics.setColor(0, 0, 0)--? print(Screen_top1.line, Screen_top1.pos, Cursor1.line, Cursor1.pos)assert(Text.le1(Screen_top1, Cursor1))Cursor_y = -1local y = Margin_top--? print('== draw')for line_index = Screen_top1.line,#Lines dolocal line = Lines[line_index]--? print('draw:', y, line_index, line)if y + Line_height > App.screen.height then break endScreen_bottom1.line = line_indexif line.mode == 'text' and line.data == '' thenline.starty = yline.startpos = 1-- insert new drawingbutton('draw', {x=4,y=y+4, w=12,h=12, color={1,1,0},icon = icon.insert_drawing,onpress1 = function()Drawing.before = snapshot(line_index-1, line_index)table.insert(Lines, line_index, {mode='drawing', y=y, h=256/2, points={}, shapes={}, pending={}})if Cursor1.line >= line_index thenCursor1.line = Cursor1.line+1endschedule_save()record_undo_event({before=Drawing.before, after=snapshot(line_index-1, line_index+1)})end})if Search_term == nil thenif line_index == Cursor1.line thenText.draw_cursor(Margin_left, y)endendScreen_bottom1.pos = Screen_top1.posy = y + Line_heightelseif line.mode == 'drawing' theny = y+Drawing_padding_topline.y = yDrawing.draw(line)y = y + Drawing.pixels(line.h) + Drawing_padding_bottomelseline.starty = yline.startpos = 1if line_index == Screen_top1.line thenline.startpos = Screen_top1.posend--? print('text.draw', y, line_index)y, Screen_bottom1.pos = Text.draw(line, line_index, line.starty, Margin_left, App.screen.width-Margin_right)y = y + Line_height--? print('=> y', y)endendif Cursor_y == -1 thenCursor_y = App.screen.heightend--? print('screen bottom: '..tostring(Screen_bottom1.pos)..' in '..tostring(Lines[Screen_bottom1.line].data))if Search_term thenText.draw_search_bar()endendfunction edit.update(dt)Cursor_time = Cursor_time + dt-- some hysteresis while resizingif Last_resize_time thenif App.getTime() - Last_resize_time < 0.1 thenreturnelseLast_resize_time = nilendendDrawing.update(dt)if Next_save and Next_save < App.getTime() thensave_to_disk(Lines, Filename)Next_save = nilendendfunction schedule_save()if Next_save == nil thenNext_save = App.getTime() + 3 -- short enough that you're likely to still remember what you didendendfunction edit.quit()-- make sure to save before quittingif Next_save thensave_to_disk(Lines, Filename)endendfunction edit.mouse_pressed(x,y, mouse_button)if Search_term then return end-- ensure cursor is visible immediately after it movesCursor_time = 0--? print('press', Selection1.line, Selection1.pos)propagate_to_button_handlers(x,y, mouse_button)for line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y, Margin_left, App.screen.width-Margin_right) then-- delicate dance between cursor, selection and old cursor/selection-- scenarios:-- regular press+release: sets cursor, clears selection-- shift press+release:-- sets selection to old cursor if not set otherwise leaves it untouched-- sets cursor-- press and hold to start a selection: sets selection on press, cursor on release-- press and hold, then press shift: ignore shift-- i.e. mousereleased should never look at shift stateOld_cursor1 = Cursor1Old_selection1 = Selection1Mousepress_shift = App.shift_down()Selection1 = {line=line_index,pos=Text.to_pos_on_line(line, x, y, Margin_left, App.screen.width-Margin_right),}--? print('selection', Selection1.line, Selection1.pos)breakendelseif line.mode == 'drawing' thenif Drawing.in_drawing(line, x, y) thenLines.current_drawing_index = line_indexLines.current_drawing = lineDrawing.before = snapshot(line_index)Drawing.mouse_pressed(line, x,y, mouse_button)breakendendendendfunction edit.mouse_released(x,y, mouse_button)if Search_term then return end--? print('release')-- ensure cursor is visible immediately after it movesCursor_time = 0if Lines.current_drawing thenDrawing.mouse_released(x,y, mouse_button)schedule_save()if Drawing.before thenrecord_undo_event({before=Drawing.before, after=snapshot(Lines.current_drawing_index)})Drawing.before = nilendelsefor line_index,line in ipairs(Lines) doif line.mode == 'text' thenif Text.in_line(line, x,y, Margin_left, App.screen.width-Margin_right) then--? print('reset selection')Cursor1 = {line=line_index,pos=Text.to_pos_on_line(line, x, y, Margin_left, App.screen.width-Margin_right),}--? print('cursor', Cursor1.line, Cursor1.pos)if Mousepress_shift thenif Old_selection1.line == nil thenSelection1 = Old_cursor1elseSelection1 = Old_selection1endendOld_cursor1, Old_selection1, Mousepress_shift = nilif eq(Cursor1, Selection1) thenSelection1 = {}endbreakendendend--? print('selection:', Selection1.line, Selection1.pos)endendfunction edit.textinput(t)-- ensure cursor is visible immediately after it movesCursor_time = 0for _,line in ipairs(Lines) do line.y = nil end -- just in case we scrollif Search_term thenSearch_term = Search_term..tSearch_text = nilText.search_next()elseif Current_drawing_mode == 'name' thenlocal before = snapshot(Lines.current_drawing_index)local drawing = Lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]p.name = p.name..trecord_undo_event({before=before, after=snapshot(Lines.current_drawing_index)})elseText.textinput(t)endschedule_save()endfunction edit.keychord_pressed(chord, key)-- ensure cursor is visible immediately after it movesCursor_time = 0if Selection1.line andnot 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) andchord ~= 'C-c' and chord ~= 'C-x' and chord ~= 'backspace' and backspace ~= 'delete' and not App.is_cursor_movement(chord) thenText.delete_selection(Margin_left, App.screen.width-Margin_right)endif Search_term thenif chord == 'escape' thenSearch_term = nilSearch_text = nilCursor1 = Search_backup.cursorScreen_top1 = Search_backup.screen_topSearch_backup = nilText.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leakselseif chord == 'return' thenSearch_term = nilSearch_text = nilSearch_backup = nilelseif chord == 'backspace' thenlocal len = utf8.len(Search_term)local byte_offset = Text.offset(Search_term, len)Search_term = string.sub(Search_term, 1, byte_offset-1)Search_text = nilelseif chord == 'down' thenCursor1.pos = Cursor1.pos+1Text.search_next()elseif chord == 'up' thenText.search_previous()endreturnelseif chord == 'C-f' thenSearch_term = ''Search_backup = {cursor={line=Cursor1.line, pos=Cursor1.pos}, screen_top={line=Screen_top1.line, pos=Screen_top1.pos}}assert(Search_text == nil)elseif chord == 'C-=' theninitialize_font_settings(Font_height+2)Text.redraw_all()elseif chord == 'C--' theninitialize_font_settings(Font_height-2)Text.redraw_all()elseif chord == 'C-0' theninitialize_font_settings(20)Text.redraw_all()elseif chord == 'C-z' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal event = undo_event()if event thenlocal src = event.beforeScreen_top1 = deepcopy(src.screen_top)Cursor1 = deepcopy(src.cursor)Selection1 = deepcopy(src.selection)patch(Lines, event.after, event.before)Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaksschedule_save()endelseif chord == 'C-y' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal event = redo_event()if event thenlocal src = event.afterScreen_top1 = deepcopy(src.screen_top)Cursor1 = deepcopy(src.cursor)Selection1 = deepcopy(src.selection)patch(Lines, event.before, event.after)Text.redraw_all() -- if we're scrolling, reclaim all fragments to avoid memory leaksschedule_save()end-- clipboardelseif chord == 'C-c' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal s = Text.selection()if s thenApp.setClipboardText(s)endelseif chord == 'C-x' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrolllocal s = Text.cut_selection(Margin_left, App.screen.width-Margin_right)if s thenApp.setClipboardText(s)endschedule_save()elseif chord == 'C-v' thenfor _,line in ipairs(Lines) do line.y = nil end -- just in case we scroll-- We don't have a good sense of when to scroll, so we'll be conservative-- and sometimes scroll when we didn't quite need to.local before_line = Cursor1.linelocal before = snapshot(before_line)local clipboard_data = App.getClipboardText()for _,code in utf8.codes(clipboard_data) dolocal c = utf8.char(code)if c == '\n' thenText.insert_return()elseText.insert_at_cursor(c)endendif Text.cursor_past_screen_bottom() thenText.snap_cursor_to_bottom_of_screen(Margin_left, App.screen.height-Margin_right)endschedule_save()record_undo_event({before=before, after=snapshot(before_line, Cursor1.line)})-- dispatch to drawing or textelseif App.mouse_down(1) or chord:sub(1,2) == 'C-' then-- DON'T reset line.y herelocal drawing_index, drawing = Drawing.current_drawing()if drawing_index thenlocal before = snapshot(drawing_index)Drawing.keychord_pressed(chord)record_undo_event({before=before, after=snapshot(drawing_index)})schedule_save()endelseif chord == 'escape' and not App.mouse_down(1) thenfor _,line in ipairs(Lines) doif line.mode == 'drawing' thenline.show_help = falseendendelseif Current_drawing_mode == 'name' thenif chord == 'return' thenCurrent_drawing_mode = Previous_drawing_modePrevious_drawing_mode = nilelselocal before = snapshot(Lines.current_drawing_index)local drawing = Lines.current_drawinglocal p = drawing.points[drawing.pending.target_point]if chord == 'escape' thenp.name = nilrecord_undo_event({before=before, after=snapshot(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({before=before, after=snapshot(Lines.current_drawing_index)})endendschedule_save()elsefor _,line in ipairs(Lines) do line.y = nil end -- just in case we scrollText.keychord_pressed(chord)endendfunction edit.key_released(key, scancode)end - edit in Manual_tests.md at line 26
* My test harness automatically runs `test_*` methods -- but only at thetop-level. I wish there was a way to raise warnings if someone defines sucha function inside a dict somewhere.