Merge lines.love
[?]
Apr 3, 2023, 4:23 PM
KWIVKQQ7AANRG6R4ZRB5TDBZ2TZTXAXIR2P6JNT362KIAJ7JQ4VQCDependencies
- [2]
HNZMFBMQMerge lines.love - [3]
Q7XPSKIIMerge lines.love - [4]
ZLJYLPOTMerge lines.love - [5]
GZ5WULJVswitch source side to new screen-line-based render - [6]
3QNOKBFMbeginnings of a test harness - [7]
4SR3Z4Y3document the version of LÖVE I've been using - [8]
ORKN6EOBMerge lines.love - [9]
3PSFWAILMerge lines.love - [10]
OGUV4HSAremove some memory leaks from rendered fragments - [11]
AVTNUQYRbasic test-enabled framework - [12]
2CTN2IEFMerge lines.love - [13]
VXORMHMEdelete experimental REPL - [14]
4YDBYBA4clean up memory leak experiments - [15]
BJ5X5O4Alet's prevent the text cursor from ever getting on a drawing - [16]
VHQCNMARseveral more modules - [17]
KMSL74GAsupport selections in the source editor - [18]
G54H3YG2get rid of all bifold text - [19]
TFM6F5ODMerge lines.love - [20]
ORRSP7FVdeduce test names on failures - [21]
PQN7CU4HMerge lines.love - [22]
TVCPXAAUrename - [23]
R5QXEHUIsomebody stop me - [24]
K74U4BAUMerge lines.love - [25]
XX7G2FFJintermingle freehand line drawings with text - [26]
MD3W5IRAnew fork: rip out drawing support - [27]
2TQUKHBCMerge lines.love - [28]
AYX33NBCMerge lines.love - [29]
ZTZOO2OQMerge lines.love - [30]
73OCE2MCafter much struggle, a brute-force undo - [31]
VP5KC4XZMerge lines.love - [32]
VHUNJHXBMerge lines.love - [33]
2L5MEZV3experiment: new edit namespace - [34]
KKMFQDR4editing source code from within the app - [35]
BULPIBEGbeginnings of a module for the text editor - [36]
D4B52CQ2Merge lines.love - [37]
TLOAPLBJadd a license - [38]
K2X6G75Zstart writing some tests for drawings - [39]
66X36NZNa little more prose describing manual_tests - [40]
A4BSGS2CMerge lines.love - [41]
JOPVPUSAediting source code from within the app - [42]
VBU5YHLRMerge lines.love - [43]
D2GCFTTTclean up repl functionality - [44]
RSZD5A7Gforgot to add json.lua - [45]
CE4LZV4Tdrop last couple of manual tests - [46]
R3KXFRZNget rid of to_text - [47]
UN7GKYV5support hyperlinks in the source editor - [48]
OI4FPFINsupport drawings in the source editor - [49]
H4R5BHVYno more Text allocations - [50]
BLWAYPKVextract a module - [51]
6LJZN727handle chords - [52]
S2QMLRXLstop creating a singleton table for every word - [53]
LXTTOB33extract a couple of files - [54]
V5SYDHPQstart thinking of compute_fragments as a detail - [55]
T4FRZSYLdelete an ancient, unused file - [56]
FS2ITYYHrecord a known issue - [57]
OTIBCAUJlove2d scaffold
Change contents
- file deletion: source_text_tests.lua source_text_tests.lua
App.screen.check(y, 'mn', 'screen:3')endfunction test_pagedown_never_moves_up() - file deletion: source_text.lua source_text.lua
State.line_cache[line_index].link_offsets = nilendfunction trim(s)return s:gsub('^%s+', ''):gsub('%s+$', '')endfunction ltrim(s)return s:gsub('^%s+', '')endfunction rtrim(s)return s:gsub('%s+$', '')edit.draw(State)return State.cursor_y == nil-- this approach is cheaper and almost works, except on the final screen-- where file ends above bottom of screen--? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1)--? local botline1 = {line=State.cursor1.line, pos=botpos}--? return Text.lt1(State.screen_bottom1, botline1)end--? print(s, e, soff, eoff, loff, hoff)return App.width(line.data:sub(1, loff-1)), App.width(line.data:sub(1, hoff))endendlocal loff = math.max(s, soff)local hoffif eoff thenhoff = math.min(e, eoff)elsehoff = eendlocal s, e = 1, 0while s <= #line.data dos, e = line.data:find('%[%[%S+%]%]', s)if s == nil then break endlocal word = line.data:sub(s+2, e-2) -- strip out surrounding '[[..]]'--? print('wikiword:', s, e, word)table.insert(line_cache.link_offsets, {s, e, word})s = e + 1endend-- Intersect the filename between byte offsets s,e with the bounds of screen line i.-- Return the left/right pixel coordinates of of the intersection,-- or nil if it doesn't intersect with screen line i.function Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e)local spos = line_cache.screen_line_starting_pos[i]local soff = Text.offset(line.data, spos)if e < soff thenreturnendlocal eoffif i < #line_cache.screen_line_starting_pos thenlocal epos = line_cache.screen_line_starting_pos[i+1]eoff = Text.offset(line.data, epos)if s > eoff thenreturnendline_cache.link_offsets = {}local pos = 1-- try to wrap at word boundariesif line_cache.link_offsets thenreturnendfunction Text.populate_link_offsets(State, line_index)local line = State.lines[line_index]pos = pos + utf8.len(frag)endendx = 0 -- new screen lineendx = x + frag_width-- try to wrap at word boundariesfor frag in line.data:gmatch('%S*%s*') dolocal frag_width = App.width(frag)--? print('-- frag:', frag, pos, x, frag_width, State.width)while x + frag_width > State.width do--? print('frag:', frag, pos, x, frag_width, State.width)if x < 0.8 * State.width then-- long word; chop it at some letter-- We're not going to reimplement TeX here.local bpos = Text.nearest_pos_less_than(frag, State.width - x)-- everything works if bpos == 0, but is a little inefficientpos = pos + bposlocal boffset = Text.offset(frag, bpos+1) -- byte _after_ bposfrag = string.sub(frag, boffset)--? if bpos > 0 then--? print('after chop:', frag)--? endfrag_width = App.width(frag)end--? print('screen line:', pos)table.insert(line_cache.screen_line_starting_pos, pos)local x = 0local pos = 1local endpos = line_cache.screen_line_starting_pos[i+1]-1local end_offset = Text.offset(line.data, endpos)return line.data:sub(offset, end_offset)endfunction Text.draw_cursor(State, x, y)-- blink every 0.5sif math.floor(Cursor_time*2)%2 == 0 thenApp.color(Cursor_color)love.graphics.rectangle('fill', x,y, 3,State.line_height)endState.cursor_x = xState.cursor_y = y+State.line_heightendfunction Text.populate_screen_line_starting_pos(State, line_index)local line = State.lines[line_index]return y, final_screen_line_starting_posendfunction Text.screen_line(line, line_cache, i)local pos = line_cache.screen_line_starting_pos[i]local offset = Text.offset(line.data, pos)if i >= #line_cache.screen_line_starting_pos thenreturn line.data:sub(offset)endy = y + State.line_heightif y >= App.screen.height thenbreakendendText.draw_cursor(State, State.left+Text.x(f, State.cursor1.pos-pos+1), y)endendendlove.graphics.print(State.search_term, State.left+lo_px,y)endelseif Focus == 'edit' thenlocal lo_px = Text.draw_highlight(State, line, State.left,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term))App.color(Text_color)if not hide_cursor and line_index == State.cursor1.line thenif pos <= State.cursor1.pos and pos + frag_len >= State.cursor1.pos thenif State.search_term thenif State.lines[State.cursor1.line].data:sub(State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)-1) == State.search_term thenselect_color(f)App.screen.print(f, State.left,y)-- render cursor if necessaryText.draw_highlight(State, line, State.left,y, pos, lo,hi)end-- render fragmentif State.selection1.line thenlocal lo, hi = Text.clip_selection(State, line_index, pos, pos+frag_len)final_screen_line_starting_pos = poslocal f = Text.screen_line(line, line_cache, i)--? print('text.draw:', f, 'at', line_index,pos, 'after', x,y)local frag_len = utf8.len(f)-- render any link decorationsfor _,link_offsets in ipairs(line_cache.link_offsets) dolocal s,e,filename = unpack(link_offsets)local lo, hi = Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e)if lo thenbutton(State, 'link', {x=State.left+lo, y=y, w=hi-lo, h=State.line_height, color={1,1,1},icon = icon.hyperlink_decoration,onpress1 = function()if file_exists(filename) thensource.switch_to_file(filename)endend,})endassert(#line_cache.screen_line_starting_pos >= 1)for i=1,#line_cache.screen_line_starting_pos dolocal pos = line_cache.screen_line_starting_pos[i]if pos < startpos then-- render nothingText.populate_link_offsets(State, line_index)initialize_color()local final_screen_line_starting_pos = startpos -- track value to returnText.populate_screen_line_starting_pos(State, line_index)-- return y for the next line, and position of start of final screen line drawnfunction Text.draw(State, line_index, y, startpos, hide_cursor) - file deletion: log_browser.lua log_browser.lua
Text.draw(State, line_index, y, --[[startpos]] 1)State.left,State.right = old_left,old_rightelseheight = log_render[line.data.name](line.data, xleft, y, xright-xleft)endendif App.mouse_x() > Log_browser_state.left and line_index == mouse_line_index thenApp.color(Cursor_line_background_color)love.graphics.rectangle('fill', xleft,y, xright-xleft, height)endy = y + heightendendendfunction render_stack_left_margin(State, line_index, line, y)if line.section_stack == nil then-- assertion messagefor k,v in pairs(line) doprint(k)endendApp.color(Section_border_color)for i=1,#line.section_stack dolocal x = State.left + (i-1)*Section_border_padding_horizontallove.graphics.line(x,y, x,y+log_browser.height(State, line_index))if y < 30 thenlove.graphics.print(line.section_stack[i].name, x+State.font_height+5, y+5, --[[vertically]] math.pi/2)endif y > App.screen.height-log_browser.height(State, line_index) then - replacement in source_text_tests.lua at line 980
App.screen.check(y, 'mno ', 'screen:3')App.screen.check(y, 'mn', 'screen:3') - replacement in source_text.lua at line 5
-- return the final y, and position of start of final screen line drawn-- return y for the next line, and position of start of final screen line drawn - replacement in source_text.lua at line 12
local x = State.leftlocal pos = 1local screen_line_starting_pos = startposlocal final_screen_line_starting_pos = startpos -- track value to return - replacement in source_text.lua at line 14
local pos = 1Text.populate_link_offsets(State, line_index) - replacement in source_text.lua at line 16
for _, f in ipairs(line_cache.fragments) doApp.color(Text_color)select_color(f)local frag_len = utf8.len(f)--? print('text.draw:', f, 'at', line_index,pos, 'after', x,y)assert(#line_cache.screen_line_starting_pos >= 1)for i=1,#line_cache.screen_line_starting_pos dolocal pos = line_cache.screen_line_starting_pos[i] - replacement in source_text.lua at line 23[6.87831]→[6.87831:87856](∅→∅),[6.87856]→[4.573:611](∅→∅),[4.611]→[6.87902:88090](∅→∅),[6.87902]→[6.87902:88090](∅→∅),[6.88090]→[6.1169:1214](∅→∅)
-- render fragmentlocal frag_width = App.width(f)if x + frag_width > State.right thenassert(x > State.left) -- no overfull linesy = y + State.line_heightif y + State.line_height > App.screen.height thenreturn y, screen_line_starting_posfinal_screen_line_starting_pos = poslocal f = Text.screen_line(line, line_cache, i)--? print('text.draw:', f, 'at', line_index,pos, 'after', x,y)local frag_len = utf8.len(f)-- render any link decorationsfor _,link_offsets in ipairs(line_cache.link_offsets) dolocal s,e,filename = unpack(link_offsets)local lo, hi = Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e)if lo thenbutton(State, 'link', {x=State.left+lo, y=y, w=hi-lo, h=State.line_height, color={1,1,1},icon = icon.hyperlink_decoration,onpress1 = function()if file_exists(filename) thensource.switch_to_file(filename)endend,}) - edit in source_text.lua at line 41
screen_line_starting_pos = posx = State.left - edit in source_text.lua at line 42
-- render fragment - replacement in source_text.lua at line 45
Text.draw_highlight(State, line, x,y, pos, lo,hi)Text.draw_highlight(State, line, State.left,y, pos, lo,hi) - replacement in source_text.lua at line 47[6.88252]→[6.7:84](∅→∅),[6.84]→[4.612:697](∅→∅),[4.697]→[6.172:367](∅→∅),[6.172]→[6.172:367](∅→∅),[6.367]→[4.698:816](∅→∅),[4.816]→[6.578:779](∅→∅),[6.578]→[6.578:779](∅→∅),[6.779]→[4.817:848](∅→∅)
-- Make [[WikiWords]] (single word, all in one screen line) clickable.local trimmed_word = rtrim(f) -- compute_fragments puts whitespace at the endif starts_with(trimmed_word, '[[') and ends_with(trimmed_word, ']]') thenlocal filename = trimmed_word:gsub('^..(.*)..$', '%1')if source.link_exists(State, filename) thenbutton(State, 'link', {x=x+App.width('[['), y=y, w=App.width(filename), h=State.line_height, color={1,1,1},icon = icon.hyperlink_decoration,onpress1 = function()source.switch_to_file(filename)end,})endendApp.screen.print(f, x,y)select_color(f)App.screen.print(f, State.left,y) - replacement in source_text.lua at line 50
if line_index == State.cursor1.line thenif pos <= State.cursor1.pos and pos + frag_len > State.cursor1.pos thenif not hide_cursor and line_index == State.cursor1.line thenif pos <= State.cursor1.pos and pos + frag_len >= State.cursor1.pos then - replacement in source_text.lua at line 54
local lo_px = Text.draw_highlight(State, line, x,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term))local lo_px = Text.draw_highlight(State, line, State.left,y, pos, State.cursor1.pos, State.cursor1.pos+utf8.len(State.search_term)) - replacement in source_text.lua at line 56
love.graphics.print(State.search_term, x+lo_px,y)love.graphics.print(State.search_term, State.left+lo_px,y) - replacement in source_text.lua at line 59
Text.draw_cursor(State, x+Text.x(f, State.cursor1.pos-pos+1), y)App.color(Text_color)Text.draw_cursor(State, State.left+Text.x(f, State.cursor1.pos-pos+1), y) - replacement in source_text.lua at line 63
x = x + frag_widthy = y + State.line_heightif y >= App.screen.height thenbreakend - edit in source_text.lua at line 68
pos = pos + frag_len - replacement in source_text.lua at line 69
if Focus == 'edit' and not hide_cursor and State.search_term == nil thenif line_index == State.cursor1.line and State.cursor1.pos == pos thenText.draw_cursor(State, x, y)endreturn y, final_screen_line_starting_posendfunction Text.screen_line(line, line_cache, i)local pos = line_cache.screen_line_starting_pos[i]local offset = Text.offset(line.data, pos)if i >= #line_cache.screen_line_starting_pos thenreturn line.data:sub(offset) - replacement in source_text.lua at line 78
return y, screen_line_starting_poslocal endpos = line_cache.screen_line_starting_pos[i+1]-1local end_offset = Text.offset(line.data, endpos)return line.data:sub(offset, end_offset) - edit in source_text.lua at line 100
-- duplicate some logic from Text.drawText.compute_fragments(State, line_index) - replacement in source_text.lua at line 101
local x = State.leftlocal x = 0 - replacement in source_text.lua at line 103[6.91829]→[6.91829:91875](∅→∅),[6.91918]→[6.91918:91941](∅→∅),[6.91941]→[4.927:963](∅→∅),[4.963]→[6.91985:92047](∅→∅),[6.91985]→[6.91985:92047](∅→∅)
for _, f in ipairs(line_cache.fragments) do-- render fragmentlocal frag_width = App.width(f)if x + frag_width > State.right thenx = State.left-- try to wrap at word boundariesfor frag in line.data:gmatch('%S*%s*') dolocal frag_width = App.width(frag)--? print('-- frag:', frag, pos, x, frag_width, State.width)while x + frag_width > State.width do--? print('frag:', frag, pos, x, frag_width, State.width)if x < 0.8 * State.width then-- long word; chop it at some letter-- We're not going to reimplement TeX here.local bpos = Text.nearest_pos_less_than(frag, State.width - x)-- everything works if bpos == 0, but is a little inefficientpos = pos + bposlocal boffset = Text.offset(frag, bpos+1) -- byte _after_ bposfrag = string.sub(frag, boffset)--? if bpos > 0 then--? print('after chop:', frag)--? endfrag_width = App.width(frag)end--? print('screen line:', pos) - edit in source_text.lua at line 124
x = 0 -- new screen line - replacement in source_text.lua at line 127
pos = pos + utf8.len(f)pos = pos + utf8.len(frag) - replacement in source_text.lua at line 131
function Text.compute_fragments(State, line_index)function Text.populate_link_offsets(State, line_index) - replacement in source_text.lua at line 135
if line_cache.fragments thenif line_cache.link_offsets then - replacement in source_text.lua at line 138
line_cache.fragments = {}local x = State.leftline_cache.link_offsets = {}local pos = 1 - replacement in source_text.lua at line 141[6.92567]→[6.92567:92611](∅→∅),[6.92611]→[4.993:1032](∅→∅),[4.1032]→[6.92837:92879](∅→∅),[6.92837]→[6.92837:92879](∅→∅),[6.93003]→[6.93003:93065](∅→∅),[6.93096]→[6.93096:93264](∅→∅),[6.93296]→[6.93296:93454](∅→∅),[6.93549]→[6.93549:93602](∅→∅),[6.93602]→[4.1033:1078](∅→∅),[4.1078]→[6.93808:93855](∅→∅),[6.93808]→[6.93808:93855](∅→∅),[6.93855]→[4.1079:1129](∅→∅),[4.1129]→[6.93929:93970](∅→∅),[6.93929]→[6.93929:93970](∅→∅),[6.93970]→[4.1130:1167](∅→∅),[4.1167]→[6.94075:94149](∅→∅),[6.94075]→[6.94075:94149](∅→∅),[6.94149]→[4.1168:1215](∅→∅)
for frag in line.data:gmatch('%S*%s*') dolocal frag_width = App.width(frag)while x + frag_width > State.right doif (x-State.left) < 0.8 * (State.right-State.left) then-- long word; chop it at some letter-- We're not going to reimplement TeX here.local bpos = Text.nearest_pos_less_than(frag, State.right - x)if bpos == 0 then break end -- avoid infinite loop when window is too narrowlocal boffset = Text.offset(frag, bpos+1) -- byte _after_ bposlocal frag1 = string.sub(frag, 1, boffset-1)local frag1_width = App.width(frag1)assert(x + frag1_width <= State.right)table.insert(line_cache.fragments, frag1)frag = string.sub(frag, boffset)frag_width = App.width(frag)endx = State.left -- new lineendif #frag > 0 thentable.insert(line_cache.fragments, frag)local s, e = 1, 0while s <= #line.data dos, e = line.data:find('%[%[%S+%]%]', s)if s == nil then break endlocal word = line.data:sub(s+2, e-2) -- strip out surrounding '[[..]]'--? print('wikiword:', s, e, word)table.insert(line_cache.link_offsets, {s, e, word})s = e + 1endend-- Intersect the filename between byte offsets s,e with the bounds of screen line i.-- Return the left/right pixel coordinates of of the intersection,-- or nil if it doesn't intersect with screen line i.function Text.clip_wikiword_with_screen_line(line, line_cache, i, s, e)local spos = line_cache.screen_line_starting_pos[i]local soff = Text.offset(line.data, spos)if e < soff thenreturnendlocal eoffif i < #line_cache.screen_line_starting_pos thenlocal epos = line_cache.screen_line_starting_pos[i+1]eoff = Text.offset(line.data, epos)if s > eoff thenreturn - replacement in source_text.lua at line 168
x = x + frag_widthendlocal loff = math.max(s, soff)local hoffif eoff thenhoff = math.min(e, eoff)elsehoff = e - edit in source_text.lua at line 176
--? print(s, e, soff, eoff, loff, hoff)return App.width(line.data:sub(1, loff-1)), App.width(line.data:sub(1, hoff)) - replacement in source_text.lua at line 1003
App.draw()edit.draw(State) - edit in source_text.lua at line 1012
function source.link_exists(State, filename)if State.link_cache == nil thenState.link_cache = {}endif State.link_cache[filename] == nil thenState.link_cache[filename] = file_exists(filename)endreturn State.link_cache[filename]end - edit in source_text.lua at line 1018
State.link_cache = {} - edit in source_text.lua at line 1021
State.line_cache[line_index].fragments = nil - edit in source_text.lua at line 1022
State.line_cache[line_index].link_offsets = nil - edit in source_edit.lua at line 187
y = y + State.line_height - edit in source_edit.lua at line 510
Text_cache = {} - edit in source.lua at line 53
-- a few text objects we can avoid recomputing unless the font changesText_cache = {} - edit in run.lua at line 9
-- a few text objects we can avoid recomputing unless the font changesText_cache = {} - replacement in log_browser.lua at line 120
y = Text.draw(State, line_index, y, --[[startpos]] 1)Text.draw(State, line_index, y, --[[startpos]] 1) - edit in edit.lua at line 118
y = y + State.line_height