R4ZFP4EHMREGFBMO324EQYDWO7CVKMBFJJBRA5LVD5UKUCU2DYSQC
V757LVMS3JLDGEPVTWCX2WEFIWDIZOTPTTGRX3IODGBTJ6IHQHNAC
JJ74YKGVDPNVI54TXGSXVS5ORMYHCWSILAMVVHQQXWUA65GIG7EAC
7ZJJOIX66VI36RFPVOLTC6RSGWVBUGNJU7MB7VZA2QAFQFDZRW3AC
ICUW7F3XQLURK4LSNPH5E3NDEFSRHKATEUHH2UPFJTMHYR3ZJF3QC
2L5MEZV344TOZLVY3432RHJFIRVXFD6O3GWLL5O4CV66BGAFTURQC
W55AT3VM73ME732N3C6NTDZ2KFA5SMYC752JHY7Z7PBGP4Z3X4RQC
R2ASHK5CEE3PTRLS37GP4PXJ7HIGJ6UD72KKBI57UDJI7VRROQGQC
RMEYIV6ZJG53HQGSIZVE4WIAR7ZHB6F2V2OYYBR6752LIL7S2PRQC
MYQNSDA3BKAVX3AKPKUDVFOL7B2O53O7U75B6Y673HJZGOULFBYAC
Q65423XUYWJ5ESAH2B63MJKJUXSTQDANPZRRMQNYTDF3YGT5HLRAC
QMRQL2FOIIXU3PCSH7UQIDRLKJN2DQHB5IUL4TLBVANNHWWETL6AC
KKQKPGCIHAG2JESQAWEMCBTAKBDC5AVIQ6LCZ2ORQM2AUCFQYLSQC
4AC6J55RTWH6FQYHQ64Y3EGF5A27Q2DHJ2XB2LXWZQEYXM53BYLAC
CRBLAWBOTECOU5MFURWCRBR43OO7NWIHP5LC35IMGTQKN73AUS6AC
GVJEOWYQMHB2YEI5O6YUGHWDNSEINZJY7XM7D5HOFC2MN24J4YRQC
WKXJNESIMLTTUCTCT7Y32R4ESTKREY7L5JDZ43MWIPM5DVM47R5AC
YXQOITYSGYQQBOMF7CJ33C2CTDAI5R2W2RCRSKJ6JTLV7MS4WVXAC
TYLURRX3NWR66WYQSCPNFKB2N42NYKCY2TSL2Z6ZK7L4KCKFZF2QC
YAU43TBB2N2EZE2REHBY3IZHC42N6PXBAVZHPVML23KCB2SIGG7QC
MBAJPTDJ4KHWACEHWYGCFMHPQYM6FQKCSIIDKWCE765UI3VTDMIAC
RXMHAZ6VL4EJMYEZM32G4W54EPF3BPWPIGASJ7ND45FJ5F6TJCZAC
3BFFX2I3V7N7QJ4Z6CKICF52IRKMSLT63MMZZ34JZPFYAZELJ4EQC
QZUFJMD5OTAZGSUO4HSJ2NNNRJR7NOKIVOIIGHWM6IF4PFNMQZHAC
CQVWNL4MQYXJVKECYVKSUMRXYMQVEMYUBHHDTDMEU5GOZ67M7QOAC
State.cursor_x = nil
State.cursor_y = nil
local y = State.top
--? print('== draw')
for line_index = State.screen_top1.line,#State.lines do
local line = State.lines[line_index]
--? print('draw:', y, line_index, line)
if y + State.line_height > App.screen.height then break end
--? print('text.draw', y, line_index)
local startpos = 1
if line_index == State.screen_top1.line then
startpos = State.screen_top1.pos
end
--? print('=> y', y)
end
if State.search_term then
Text.draw_search_bar(State)
end
end
function edit.update(State, dt)
if State.next_save and State.next_save < Current_time then
save_to_disk(State)
State.next_save = nil
end
end
function schedule_save(State)
if State.next_save == nil then
State.next_save = Current_time + 3 -- short enough that you're likely to still remember what you did
end
end
function edit.quit(State)
-- make sure to save before quitting
if State.next_save then
save_to_disk(State)
-- give some time for the OS to flush everything to disk
love.timer.sleep(0.1)
end
end
function edit.mouse_press(State, x,y, mouse_button)
--? print_and_log(('edit.mouse_press: cursor at %d,%d'):format(State.cursor1.line, State.cursor1.pos))
if y < State.top then
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=State.screen_top1.line,
pos=State.screen_top1.pos,
}
return
end
for line_index,line in ipairs(State.lines) do
if Text.in_line(State, line_index, x,y) 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. mouse_release should never look at shift state
--? print_and_log(('edit.mouse_press: in line %d'):format(line_index))
State.old_cursor1 = State.cursor1
State.old_selection1 = State.selection1
State.mousepress_shift = App.shift_down()
State.selection1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
return
end
end
for line_index,line in ipairs(State.lines) do
if Text.in_line(State, line_index, x,y) then
--? print_and_log(('edit.mouse_release: in line %d'):format(line_index))
State.cursor1 = {
line=line_index,
pos=Text.to_pos_on_line(State, line_index, x, y),
}
--? print_and_log(('edit.mouse_release: cursor now %d,%d'):format(State.cursor1.line, State.cursor1.pos))
for i=1,math.floor(-dy) do
Text.down(State)
end
end
end
function edit.text_input(State, t)
--? print('text input', t)
if State.search_term then
State.search_term = State.search_term..t
Text.search_next(State)
else
Text.text_input(State, t)
end
schedule_save(State)
end
function edit.keychord_press(State, chord, key)
if State.selection1.line 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) and
Text.delete_selection(State, State.left, State.right)
end
if State.search_term then
if chord == 'escape' then
State.search_term = nil
State.cursor1 = State.search_backup.cursor
State.screen_top1 = State.search_backup.screen_top
State.search_backup = nil
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
record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
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