3BFFX2I3V7N7QJ4Z6CKICF52IRKMSLT63MMZZ34JZPFYAZELJ4EQC
YAU43TBB2N2EZE2REHBY3IZHC42N6PXBAVZHPVML23KCB2SIGG7QC
RXMHAZ6VL4EJMYEZM32G4W54EPF3BPWPIGASJ7ND45FJ5F6TJCZAC
ICUW7F3XQLURK4LSNPH5E3NDEFSRHKATEUHH2UPFJTMHYR3ZJF3QC
73OCE2MCBJJZZMN2KYPJTBOUCKBZAOQ2QIAMTGCNOOJ2AJAXFT2AC
4AC6J55RTWH6FQYHQ64Y3EGF5A27Q2DHJ2XB2LXWZQEYXM53BYLAC
ZLJYLPOTXIVBVWJ4NTRM2YCQPT2FCSN7446P56MJFEFY45QTB7IAC
VJ77YABHVJZWJKLHAGIPC562GYM73AUGRLCP4JLKP5JPWPT2RIHAC
4Z4XY6TK3DU775XME2V2FVDN7O3D3W6DO6N4OGLGVIL6M6RPCKPQC
LLQC2M2IMEJBJQXZTKC3OAKG5WKHSERXKAKCYHQRUZZD6CVRIHAQC
2QLTVKM6MVC25L44BSHKPZAXOOLK2VAUCGCYRUDRXXNYNSWDIWQAC
BULPIBEGL7TMK6CVIE7IS7WGAHGOSUJBGJSFQK542MOWGHP2ADQQC
Q65423XUYWJ5ESAH2B63MJKJUXSTQDANPZRRMQNYTDF3YGT5HLRAC
H2DPLWMVRFYTO2CQTG54FMT2LF3B6UKLXH32CUA22DNQJVP5XBNQC
7ZXZGVHM2THAGJABJZLKW56VG72AD3Z6DINZ7XBWOYDJX4J77PMQC
AQMZJXUR5NFNATJ4LPTVGVLQFIKRKRSPYAICXWHGQCQ4WLMQ2JTQC
2F5RZ4YSLVCVTYEUDJ4D6H355YD2B65ZRMTPGEEKIA2Q7FGIC3WAC
LK4ZW4BBDD5LC4JK4XK5DJESSDFAIRVFPDM324S7SCAUXEXYVTLQC
F52PSTYSIIUD7UPEVLEJPVSBQU7IKVHM4GN4YASNSKFP4MRE6S6AC
ZS5IYZH5EXXPSVIFWS7XW5POEVRRCK6XV6PB36D3EJXJRT22LKOQC
MXA3RZYKUI4UF2ISY7JEF6VKX6NOPZMZH5SLLCZHRJKFIXXXDPSAC
QAMVLUK22RP5RBDTDV5XVPQCSJUWDWESV4TRCUTNUM46E26BH2AQC
QCPXQ2E3USF3Z6R6WJ2JKHTRMPKA6QWXFKKRMLXA3MXABJEL543AC
3PSFWAILGRA4OYXWS2DX7VF332AIBPYBXHEA4GIQY2XEJVD65UMAC
JOPVPUSAMMU6RFVDQR4NJC4GNNUFB7GPKVH7OS5FKCYS5QZ53VLQC
KKQKPGCIHAG2JESQAWEMCBTAKBDC5AVIQ6LCZ2ORQM2AUCFQYLSQC
3VHUIIATPOF7FXB7NTL5MESCV5BCQACII2D7QZ4UIUCBX3CWXMMAC
KHOQZJWF3SNBCKLU5A7YDKPI6QHKZN7TERN2CRLB7GHG5GYBNNLAC
2TQUKHBC2EB3WDBD5UL62DQYV7CV6B7OJYK7CHOEDNOZENSOG42AC
CRBLAWBOTECOU5MFURWCRBR43OO7NWIHP5LC35IMGTQKN73AUS6AC
5XMBCKJZ7YCUOOQWWZRNLXMXD3MUYC2VAF3VKA4BIEHQRPDQMUMAC
D4B52CQ2QKG2HQKFUQOO5S2ME325DTW3PH2D7SBXCW4BPQFYG7CAC
IDS27PX6ITX6XVQ7PAPNGQBQUHBSM4WY2TBI4PU37PCJBYTVJ6UAC
R2ASHK5CEE3PTRLS37GP4PXJ7HIGJ6UD72KKBI57UDJI7VRROQGQC
2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC
TYLURRX3NWR66WYQSCPNFKB2N42NYKCY2TSL2Z6ZK7L4KCKFZF2QC
QMRQL2FOIIXU3PCSH7UQIDRLKJN2DQHB5IUL4TLBVANNHWWETL6AC
MBAJPTDJ4KHWACEHWYGCFMHPQYM6FQKCSIIDKWCE765UI3VTDMIAC
end
return event
end
function patch(lines, from, to)
--? if #from.lines == 1 and #to.lines == 1 then
--? assert(from.start_line == from.end_line)
--? assert(to.start_line == to.end_line)
--? assert(from.start_line == to.start_line)
--? lines[from.start_line] = to.lines[1]
--? return
--? end
function patch_placeholders(line_cache, from, to)
assert(from.start_line == to.start_line, 'failed to patch undo operation')
for i=from.end_line,from.start_line,-1 do
table.remove(line_cache, i)
end
assert(#to.lines == to.end_line-to.start_line+1, 'failed to patch undo operation')
for i=1,#to.lines do
table.insert(line_cache, to.start_line+i-1, {})
end
end
-- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080
function deepcopy(obj, seen)
if type(obj) ~= 'table' then return obj end
if seen and seen[obj] then return seen[obj] end
local s = seen or {}
local result = setmetatable({}, getmetatable(obj))
s[obj] = result
for k,v in pairs(obj) do
result[deepcopy(k, s)] = deepcopy(v, s)
end
return result
end
function minmax(a, b)
return math.min(a,b), math.max(a,b)
end
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
lines={},
start_line=s,
end_line=e,
-- no filename; undo history is cleared when filename changes
}
end
return event
end
function patch(lines, from, to)
--? if #from.lines == 1 and #to.lines == 1 then
--? assert(from.start_line == from.end_line)
--? assert(to.start_line == to.end_line)
--? assert(from.start_line == to.start_line)
--? lines[from.start_line] = to.lines[1]
--? return
--? end
local line = State.lines[i]
if line.mode == 'text' then
elseif line.mode == 'drawing' then
else
end
table.insert(event.lines, deepcopy(State.lines[i]))
end
end
function patch_placeholders(line_cache, from, to)
assert(from.start_line == to.start_line, 'failed to patch undo operation')
for i=from.end_line,from.start_line,-1 do
table.remove(line_cache, i)
end
-- https://stackoverflow.com/questions/640642/how-do-you-copy-a-lua-table-by-value/26367080#26367080
function deepcopy(obj, seen)
if type(obj) ~= 'table' then return obj end
if seen and seen[obj] then return seen[obj] end
local s = seen or {}
local result = setmetatable({}, getmetatable(obj))
s[obj] = result
for k,v in pairs(obj) do
result[deepcopy(k, s)] = deepcopy(v, s)
end
return result
end
function minmax(a, b)
return math.min(a,b), math.max(a,b)
end
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
--? print('clearing line caches')
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
Text.redraw_all(State) -- if we're scrolling, reclaim all line caches to avoid memory leaks
-- fragments: snippets of the line guaranteed to not straddle screen lines
-- screen_line_starting_pos: optional array of grapheme indices if it wraps over more than one screen line
-- screen_line_starting_pos: optional array of codepoint indices if it wraps over more than one screen line
elseif chord == 'return' then
State.search_term = nil
State.search_backup = nil
elseif chord == 'backspace' then
local len = utf8.len(State.search_term)
local byte_offset = Text.offset(State.search_term, len)
State.search_term = string.sub(State.search_term, 1, byte_offset-1)
elseif chord == 'down' then
State.cursor1.pos = State.cursor1.pos+1
Text.search_next(State)
elseif chord == 'up' then
Text.search_previous(State)
end
return
elseif chord == 'C-f' then
State.search_term = ''
State.search_backup = {
cursor={line=State.cursor1.line, pos=State.cursor1.pos},
screen_top={line=State.screen_top1.line, pos=State.screen_top1.pos},
}
-- zoom
elseif chord == 'C-=' then
edit.update_font_settings(State, State.font_height+2)
Text.redraw_all(State)
elseif chord == 'C--' then
if State.font_height > 2 then
edit.update_font_settings(State, State.font_height-2)
Text.redraw_all(State)
end
elseif chord == 'C-0' then
edit.update_font_settings(State, 20)
Text.redraw_all(State)
-- undo
elseif chord == 'C-z' then
local event = undo_event(State)
if event then
local src = event.before
State.screen_top1 = deepcopy(src.screen_top)
State.cursor1 = deepcopy(src.cursor)
State.selection1 = deepcopy(src.selection)
patch(State.lines, event.after, event.before)
schedule_save(State)
end
elseif chord == 'C-y' then
local event = redo_event(State)
if event then
local src = event.after
State.screen_top1 = deepcopy(src.screen_top)
State.cursor1 = deepcopy(src.cursor)
State.selection1 = deepcopy(src.selection)
patch(State.lines, event.before, event.after)
schedule_save(State)
end
-- clipboard
elseif chord == 'C-a' then
State.selection1 = {line=1, pos=1}
State.cursor1 = {line=#State.lines, pos=utf8.len(State.lines[#State.lines].data)+1}
elseif chord == 'C-c' then
local s = Text.selection(State)
if s then
App.set_clipboard(s)
end
elseif chord == 'C-x' then
local s = Text.cut_selection(State, State.left, State.right)
if s then
App.set_clipboard(s)
end
schedule_save(State)
elseif chord == 'C-v' then
-- 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 = State.cursor1.line
local before = snapshot(State, before_line)
local clipboard_data = App.get_clipboard()
for _,code in utf8.codes(clipboard_data) do
local c = utf8.char(code)
if c == '\n' then
Text.insert_return(State)
else
Text.insert_at_cursor(State, c)
end
end
if Text.cursor_out_of_screen(State) then
Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
end
schedule_save(State)
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
-- dispatch to text
else
Text.keychord_press(State, chord)
end
end
function edit.key_release(State, key, scancode)
end
function edit.update_font_settings(State, font_height)
State.font_height = font_height
State.line_height = math.floor(font_height*1.3)
end
--== some methods for tests
-- Insulate tests from some key globals so I don't have to change the vast
-- majority of tests when they're modified for the real app.
Test_margin_left = 25
Test_margin_right = 0
function edit.initialize_test_state()
-- if you change these values, tests will start failing
return edit.initialize_state(
15, -- top margin
Test_margin_left,
App.screen.width - Test_margin_right,
15) -- line height
end
-- all text_input events are also keypresses
-- TODO: handle chords of multiple keys
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 = {}
edit.update(State, 0)
edit.draw(State)
end
-- not all keys are text_input
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
function edit.run_after_mouse_click(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button)
App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
function edit.run_after_mouse_press(State, x,y, mouse_button)
App.fake_mouse_press(x,y, mouse_button)
edit.mouse_press(State, x,y, mouse_button)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end
function edit.run_after_mouse_release(State, x,y, mouse_button)
App.fake_mouse_release(x,y, mouse_button)
edit.mouse_release(State, x,y, mouse_button)
App.screen.contents = {}
edit.update(State, 0)
edit.draw(State)
end