So far I've just changed how existing variables are organized, and put some scaffolding in place for dealing with the new types. Next up: rewriting the code for scrolling to something that feels more obviously correct.
2RXZ3PGOTTZ6M4R372JXIKPLBQKPVBMAXNPIEO2HZDN4EMYW4GNAC
O6T3TPXDUSZKH2JHNHWIMSEV3UADIHHF26IYA44X3RCRXNUXEKBQC
2ZYV7D3W2HPQW2HYB7XDPM4T7KEWPUFPZ77BDLCCDSCLRPJFK6PQC
GE56XURAWLO62DQKV4KJBJR4RGN2MVHQME2B44VVTCBYTBPZRP4AC
3TFEAQSWVFGSH3ISZ4Q3DFR3YPPWHEIBUEVR3XWB7QX6VKHW455QC
PYGMASTVHDTGX3LDTL364UWXEHVSWQ7STAJLZZI5YY6EA6EEICOAC
H2DPLWMVRFYTO2CQTG54FMT2LF3B6UKLXH32CUA22DNQJVP5XBNQC
MGT5FTJ35MGYCQO3TZVK3RYUIN5YX475R4XG7RO42SYLYF4AIKFAC
BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC
WLHI7KD3LJTQH6V7RLVJWGZUR4YQK6LN4OIUMIN45BGMMQGN6RNQC
JY4VK7L2JKRWRV45QEMGLWPFAQRUWKFHMAL6DWNYEDCKO5Y4W5FQC
537TQ2QNPKPG322I4OIMN5IY22S45Z42LEBBZ2IN5MVM355BEJTAC
XNFTJHC4QSHNSIWNN7K6QZEZ37GTQYKHS4EPNSVPQCUSWREROGIQC
5DOC2CBMBDMAOJ7IKLDGVRCY4SNPCJTTF7DK7WGNLPGNV4AWVJNAC
DAENUOGV7KR6MZVXS36HEN3SZC4RFIS6REGAFVBOFEPO76EUDGIAC
DLQMM2656JHXX3ONOEM6UIOXKFJFT5QT7RHWK7YS2W77PVZWHRSAC
SVJZZDC3K6AKAXHGRNAZKRE2ZXEKJANNLG7LSSUZJARFBL5F7C4AC
3CSIZJ33MAZTTJUJX7H2VDJRGZ3A5AWKVSAQIMV3UQACVWNZA6ZAC
C42QQZSFFGU6DZ73MCPGYZJQ675YTMEOJAPQLHKRJLWQH5GMWHMQC
HBZ2UCUFM6EYLFZGUQVJDCLANO4UXYMBOU3TFPB2JASJMB53ZGXAC
PR4KIAZDOBQMEUOV2G7ZEZUW3E4L5ZCHYSS7PTYWGXPSNVRAGHCAC
2POFQQLW42ZQCF7NBTIFLYKXBYT5PVSC3T5UOURIEPYNFVBN2MKAC
KJKKASHZCC5JD6G6PWQ4TA42NVI2CNTAZ667GA76H272DD6KCNRQC
242L3OQXTU2TCAINRJXQEEDSXQXM7Y7USUPBK37ZNM3A7V5TUDSAC
TRNWIQN6RPLDLYWULLKG5L255E7E3DPNGLCSLAF6IJWYQRCCLARQC
HIH47LNBDXHB2PU2HPPG47IBX5QKJXB74G2SZ5B3ZUYYPTYR3TYAC
HYEAFRZ2UEKDYTAE2GDQLHEJBPQASP2NDLMXB7F6MTVK2BKOXKEAC
PHQPLJUQZOYZ7B3IDADDANMVXLKIKTU5DRSSEWTSDYCSDKX7M7JAC
BOFNXP5GZDCUMQG3LQVTSSFEQP7REQ4RIRJLDLETFSAGFTVDVEKAC
5L7K4GBDEAFH44LMLNKVFMHLWDNXXBKRPEI347VE5ZLXVFSMD2FAC
AVQ5MC5DWNLI6LUUIPGBLGP4LKRPGWBY4THNY25OBT2FAVHC6MCAC
OTIBCAUJ3KDQJLVDN3A536DLZGNRYMGJLORZVR3WLCGXGO6UGO6AC
MGOQ5XAVFTWZPBG2O5ZTGSEKU6BRJKQZLDV6CM4737VD2FAEB5JQC
JCSLDGAH2F6AIY4Z6XM6K4LOMW7EFY3E4NF5YXLMHLTYTX3A4Z3QC
A2QPFRFJNWDHBYRRLJFBK5BOTOWXDT5DYCKHRRKVBZNDA4NE3CHQC
OIB2QPRCB4MAVZV5NCEKSAL45ITT6V4BYSET3Q2VCT3WBOIC4QVQC
2C7CTIQYDDYVQJNKX2OSHZ6VMAMPOGNUVTSFAUV7HQCPMZR2YRUAC
BJ5X5O4ACBBJ56LRBBSTCW6IBQP4HAEOOOPNH3SKTA4F66YTOIDAC
Y6FTGOHJH2OQTJVB2GQTJNXSZFGI2QWAS3V2NOFM64O5U5RNVKDQC
7IKRRESBHMYHHKW4XHUEEKHKPOBLAGZ7A7FJMRU32MTRKIV6S7GQC
DXT4QTAH5G6J7ZB3SMOOXVECKWYUPZVE2ODMUFTPPNHLTOSZLQSAC
252M2QMDBMNWHBZY5MDSC7WVYO5JBLJYPVMW5W4IVJCZVYRQ5IQQC
if Debug_new_render then print('checking to draw', pos, Top_screen_line_starting_pos) end
if line_index > Screen_top_line or pos >= Top_screen_line_starting_pos then
if Debug_new_render then print('checking to draw', pos, Screen_top1.pos) end
if line_index > Screen_top1.line or pos >= Screen_top1.pos then
if line_index == Cursor_line then
if pos <= Cursor_pos and pos + frag_len > Cursor_pos then
Text.draw_cursor(x+Text.cursor_x2(frag, Cursor_pos-pos+1), y)
if line_index == Cursor1.line then
if pos <= Cursor1.pos and pos + frag_len > Cursor1.pos then
Text.draw_cursor(x+Text.cursor_x2(frag, Cursor1.pos-pos+1), y)
Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_offset)..t..string.sub(Lines[Cursor_line].data, byte_offset+1)
Lines[Cursor_line].fragments = nil
Cursor_pos = Cursor_pos+1
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_offset)..t..string.sub(Lines[Cursor1.line].data, byte_offset+1)
Lines[Cursor1.line].fragments = nil
Cursor1.pos = Cursor1.pos+1
local byte_offset = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
table.insert(Lines, Cursor_line+1, {mode='text', data=string.sub(Lines[Cursor_line].data, byte_offset)})
Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_offset-1)
Lines[Cursor_line].fragments = nil
Cursor_line = Cursor_line+1
Cursor_pos = 1
local byte_offset = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos)
table.insert(Lines, Cursor1.line+1, {mode='text', data=string.sub(Lines[Cursor1.line].data, byte_offset)})
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_offset-1)
Lines[Cursor1.line].fragments = nil
Cursor1.line = Cursor1.line+1
Cursor1.pos = 1
assert(Lines[Cursor_line].mode == 'text')
if Cursor_pos <= utf8.len(Lines[Cursor_line].data) then
Cursor_pos = Cursor_pos+1
assert(Lines[Cursor1.line].mode == 'text')
if Cursor1.pos <= utf8.len(Lines[Cursor1.line].data) then
Cursor1.pos = Cursor1.pos+1
if Cursor_pos > 1 then
local byte_start = utf8.offset(Lines[Cursor_line].data, Cursor_pos-1)
local byte_end = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
if Cursor1.pos > 1 then
local byte_start = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos-1)
local byte_end = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos)
Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)..string.sub(Lines[Cursor_line].data, byte_end)
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_start-1)..string.sub(Lines[Cursor1.line].data, byte_end)
Cursor_pos = utf8.len(Lines[Cursor_line-1].data)+1
Lines[Cursor_line-1].data = Lines[Cursor_line-1].data..Lines[Cursor_line].data
Lines[Cursor_line-1].fragments = nil
table.remove(Lines, Cursor_line)
Cursor1.pos = utf8.len(Lines[Cursor1.line-1].data)+1
Lines[Cursor1.line-1].data = Lines[Cursor1.line-1].data..Lines[Cursor1.line].data
Lines[Cursor1.line-1].fragments = nil
table.remove(Lines, Cursor1.line)
if Cursor_pos <= utf8.len(Lines[Cursor_line].data) then
local byte_start = utf8.offset(Lines[Cursor_line].data, Cursor_pos)
local byte_end = utf8.offset(Lines[Cursor_line].data, Cursor_pos+1)
if Cursor1.pos <= utf8.len(Lines[Cursor1.line].data) then
local byte_start = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos)
local byte_end = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos+1)
Lines[Cursor_line].data = string.sub(Lines[Cursor_line].data, 1, byte_start-1)..string.sub(Lines[Cursor_line].data, byte_end)
Lines[Cursor1.line].data = string.sub(Lines[Cursor1.line].data, 1, byte_start-1)..string.sub(Lines[Cursor1.line].data, byte_end)
Lines[Cursor_line].data = Lines[Cursor_line].data..Lines[Cursor_line+1].data
Lines[Cursor_line].fragments = nil
table.remove(Lines, Cursor_line+1)
Lines[Cursor1.line].data = Lines[Cursor1.line].data..Lines[Cursor1.line+1].data
Lines[Cursor1.line].fragments = nil
table.remove(Lines, Cursor1.line+1)
Cursor_line = new_cursor_line
if Lines[Cursor_line].screen_line_starting_pos == nil then
Cursor_pos = Text.nearest_cursor_pos(Lines[Cursor_line].data, Cursor_x)
Cursor1.line = new_cursor_line
if Lines[Cursor1.line].screen_line_starting_pos == nil then
Cursor1.pos = Text.nearest_cursor_pos(Lines[Cursor1.line].data, Cursor_x)
if Screen_top_line == Cursor_line and Top_screen_line_starting_pos == screen_line_starting_pos then
Top_screen_line_starting_pos = screen_line_starting_pos
--? print('pos of top of screen is also '..tostring(Top_screen_line_starting_pos)..' of the same line')
if Screen_top1.line == Cursor1.line and Screen_top1.pos == screen_line_starting_pos then
Screen_top1.pos = screen_line_starting_pos
--? print('pos of top of screen is also '..tostring(Screen_top1.pos)..' of the same line')
local s = string.sub(Lines[Cursor_line].data, screen_line_starting_pos)
Cursor_pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, Cursor_x) - 1
local s = string.sub(Lines[Cursor1.line].data, screen_line_starting_pos)
Cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(s, Cursor_x) - 1
if Screen_top_line == Cursor_line and Top_screen_line_starting_pos == screen_line_starting_pos then
Top_screen_line_starting_pos = new_screen_line_starting_pos
--? print('also setting pos of top of screen to '..tostring(Top_screen_line_starting_pos))
if Screen_top1.line == Cursor1.line and Screen_top1.pos == screen_line_starting_pos then
Screen_top1.pos = new_screen_line_starting_pos
--? print('also setting pos of top of screen to '..tostring(Screen_top1.pos))
local s = string.sub(Lines[Cursor_line].data, new_screen_line_starting_pos)
Cursor_pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, Cursor_x) - 1
--? print('cursor pos is now '..tostring(Cursor_pos))
local s = string.sub(Lines[Cursor1.line].data, new_screen_line_starting_pos)
Cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, Cursor_x) - 1
--? print('cursor pos is now '..tostring(Cursor1.pos))
Cursor_line = new_cursor_line
Cursor_pos = Text.nearest_cursor_pos(Lines[Cursor_line].data, Cursor_x)
--? print(Cursor_pos)
Cursor1.line = new_cursor_line
Cursor1.pos = Text.nearest_cursor_pos(Lines[Cursor1.line].data, Cursor_x)
--? print(Cursor1.pos)
--? print(Cursor_line, Cursor_pos, Screen_bottom_line)
if Cursor_line > Screen_bottom_line then
--? print('screen top before:', Screen_top_line, Top_screen_line_starting_pos)
Screen_top_line = Cursor_line
--? print(Cursor1.line, Cursor1.pos, Screen_bottom1.line)
if Cursor1.line > Screen_bottom1.line then
--? print('screen top before:', Screen_top1.line, Screen_top1.pos)
Screen_top1.line = Cursor1.line
local s = string.sub(Lines[Cursor_line].data, new_screen_line_starting_pos)
Cursor_pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, Cursor_x) - 1
--? print('cursor pos is now '..tostring(Cursor_pos))
Screen_top_line = Cursor_line
local s = string.sub(Lines[Cursor1.line].data, new_screen_line_starting_pos)
Cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(s, Cursor_x) - 1
--? print('cursor pos is now '..tostring(Cursor1.pos))
Screen_top1.line = Cursor1.line
for i=#Lines[Cursor_line].screen_line_starting_pos,1,-1 do
local spos = Lines[Cursor_line].screen_line_starting_pos[i]
if spos <= Cursor_pos then
for i=#Lines[Cursor1.line].screen_line_starting_pos,1,-1 do
local spos = Lines[Cursor1.line].screen_line_starting_pos[i]
if spos <= Cursor1.pos then
i=#Lines[Cursor_line].screen_line_starting_pos
local spos = Lines[Cursor_line].screen_line_starting_pos[i]
return spos <= Cursor_pos
i=#Lines[Cursor1.line].screen_line_starting_pos
local spos = Lines[Cursor1.line].screen_line_starting_pos[i]
return spos <= Cursor1.pos
if Lines[Screen_top_line-1].mode == 'drawing' then
h = 20 + Drawing.pixels(Lines[Screen_top_line-1].h)
elseif Lines[Screen_top_line-1].screen_line_starting_pos == nil then
if Lines[Screen_top1.line-1].mode == 'drawing' then
h = 20 + Drawing.pixels(Lines[Screen_top1.line-1].h)
elseif Lines[Screen_top1.line-1].screen_line_starting_pos == nil then
function Text.to2(pos1)
local result = {line=pos1.line, screen_line=1}
if Line[pos1.line].screen_line_starting_pos == nil then
result.screen_pos = pos1.pos
else
for i=#Lines[pos1.line].screen_line_starting_pos,1,-1 do
local spos = Lines[pos1.line].screen_line_starting_pos[i]
if spos <= Cursor1.pos then
result.screen_line = i
result.screen_pos = spos
break
end
end
end
assert(result.screen_pos)
return result
end
function Text.to1(pos2)
local result = {line=pos2.line, pos=pos2.screen_pos}
if pos2.screen_line > 1 then
result.pos = Lines[pos2.line].screen_line_starting_pos[pos2.screen_line] + pos2.screen_pos
end
return result
end
Cursor_line = 1
Cursor_pos = 1 -- in Unicode codepoints, from 1 to utf8.len(line) + 1
-- 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.
Cursor1 = {line=1, pos=1} -- position of cursor
Screen_top1 = {line=1, pos=1} -- position of start of screen line at top of screen
Screen_bottom1 = {line=1, pos=1} -- position of start of screen line at bottom of screen
-- scrolling support
Screen_top_line = 1
Screen_bottom_line = 1
Top_screen_line_starting_pos = 1 -- when top of screen starts in between a wrapped line
Screen_top_line = Screen_bottom_line
Cursor_line = Screen_top_line
Top_screen_line_starting_pos = 1
Cursor_pos = Top_screen_line_starting_pos
Screen_top1.line = Screen_bottom1.line
Screen_top1.pos = 1
Cursor1.line = Screen_top1.line
Cursor1.pos = Screen_top1.pos
if Lines[Screen_top_line].mode == 'drawing' then
y = y - Drawing.pixels(Lines[Screen_top_line].h)
if Lines[Screen_top1.line].mode == 'drawing' then
y = y - Drawing.pixels(Lines[Screen_top1.line].h)