2022-06-03 22:14:53 +01:00
-- text editor, particularly text drawing, horizontal wrap, vertical scrolling
2022-05-18 04:29:08 +01:00
Text = { }
2024-09-01 02:48:40 +01:00
function Text . draw_cursor ( editor , x , y )
2022-06-09 21:45:52 +01:00
-- blink every 0.5s
if math.floor ( Cursor_time * 2 ) % 2 == 0 then
2022-07-12 07:03:27 +01:00
App.color ( Cursor_color )
2024-09-01 02:48:40 +01:00
love.graphics . rectangle ( ' fill ' , x , y , 3 , editor.line_height )
2022-06-09 21:45:52 +01:00
end
2022-05-20 06:58:04 +01:00
end
2024-09-01 02:48:40 +01:00
function Text . text_input ( editor , t )
2024-06-20 05:39:47 +01:00
if love.mouse . isDown ( 1 ) then return end
2024-06-22 17:43:02 +01:00
if any_modifier_down ( ) then
if Keys_down [ t ] then
2024-07-17 22:32:44 +01:00
-- The modifiers didn't change the key. Handle it in keychord_press.
2023-11-25 23:20:55 +00:00
return
else
-- Key mutated by the keyboard layout. Continue below.
end
end
2024-09-01 02:48:40 +01:00
local before = snapshot ( editor , editor.cursor . line )
Text.insert_at_cursor ( editor , t )
maybe_snap_cursor_to_bottom_of_screen ( editor )
record_undo_event ( editor , { before = before , after = snapshot ( editor , editor.cursor . line ) } )
2024-07-17 22:10:35 +01:00
end
2024-09-01 02:48:40 +01:00
function Text . insert_at_cursor ( editor , t )
assert ( editor.lines [ editor.cursor . line ] . mode == ' text ' , ' line is not text ' )
local byte_offset = Text.offset ( editor.lines [ editor.cursor . line ] . data , editor.cursor . pos )
editor.lines [ editor.cursor . line ] . data = string.sub ( editor.lines [ editor.cursor . line ] . data , 1 , byte_offset - 1 ) .. t .. string.sub ( editor.lines [ editor.cursor . line ] . data , byte_offset )
editor.cursor . pos = editor.cursor . pos + 1
2024-07-17 22:10:35 +01:00
end
2022-12-24 01:16:19 +00:00
-- Don't handle any keys here that would trigger text_input above.
2024-09-01 02:48:40 +01:00
function Text . keychord_press ( editor , chord )
assert ( editor.lines [ editor.cursor . line ] . mode == editor.cursor . mode )
2024-07-17 05:33:59 +01:00
--== shortcuts that mutate text (must schedule_save)
2022-05-18 06:36:10 +01:00
if chord == ' return ' then
2024-09-01 02:48:40 +01:00
local before_line = editor.cursor . line
local before = snapshot ( editor , before_line )
Text.insert_return ( editor )
maybe_snap_cursor_to_bottom_of_screen ( editor )
record_undo_event ( editor , { before = before , after = snapshot ( editor , before_line , editor.cursor . line ) } )
schedule_save ( editor )
2022-05-19 04:22:57 +01:00
elseif chord == ' tab ' then
2024-09-01 02:48:40 +01:00
if editor.cursor . mode == ' text ' then
local before = snapshot ( editor , editor.cursor . line )
Text.insert_at_cursor ( editor , ' \t ' )
maybe_snap_cursor_to_bottom_of_screen ( editor )
record_undo_event ( editor , { before = before , after = snapshot ( editor , editor.cursor . line ) } )
schedule_save ( editor )
2022-05-29 16:12:47 +01:00
end
2024-06-27 06:35:28 +01:00
elseif chord == ' backspace ' then
2024-09-01 02:48:40 +01:00
if editor.cursor . mode == ' text ' then
if editor.selection1 . line then
2024-09-01 03:01:33 +01:00
Text.delete_selection_and_record_undo_event ( editor )
2024-09-01 02:48:40 +01:00
schedule_save ( editor )
2024-06-27 06:35:28 +01:00
return
end
local before
2024-09-01 02:48:40 +01:00
if editor.cursor . pos > 1 then
before = snapshot ( editor , editor.cursor . line )
local byte_start = utf8.offset ( editor.lines [ editor.cursor . line ] . data , editor.cursor . pos - 1 )
local byte_end = utf8.offset ( editor.lines [ editor.cursor . line ] . data , editor.cursor . pos )
2024-06-27 06:35:28 +01:00
if byte_start then
if byte_end then
2024-09-01 02:48:40 +01:00
editor.lines [ editor.cursor . line ] . data = string.sub ( editor.lines [ editor.cursor . line ] . data , 1 , byte_start - 1 ) .. string.sub ( editor.lines [ editor.cursor . line ] . data , byte_end )
2024-06-27 06:35:28 +01:00
else
2024-09-01 02:48:40 +01:00
editor.lines [ editor.cursor . line ] . data = string.sub ( editor.lines [ editor.cursor . line ] . data , 1 , byte_start - 1 )
2024-06-27 06:35:28 +01:00
end
2024-09-01 02:48:40 +01:00
editor.cursor . pos = editor.cursor . pos - 1
2024-06-27 06:35:28 +01:00
end
2024-09-01 02:48:40 +01:00
elseif editor.cursor . line > 1 then
before = snapshot ( editor , editor.cursor . line - 1 , editor.cursor . line )
if editor.lines [ editor.cursor . line - 1 ] . mode == ' drawing ' then
table.remove ( editor.lines , editor.cursor . line - 1 )
if editor.screen_top . line == editor.cursor . line - 1 then
editor.screen_top = { mode = ' text ' , line = editor.screen_top . line , pos = 1 }
2024-09-01 01:53:19 +01:00
end
2022-05-18 06:36:10 +01:00
else
2024-06-27 06:35:28 +01:00
-- join lines
2024-09-01 02:48:40 +01:00
editor.cursor . pos = utf8.len ( editor.lines [ editor.cursor . line - 1 ] . data ) + 1
editor.lines [ editor.cursor . line - 1 ] . data = editor.lines [ editor.cursor . line - 1 ] . data .. editor.lines [ editor.cursor . line ] . data
table.remove ( editor.lines , editor.cursor . line )
2022-05-18 06:36:10 +01:00
end
2024-09-01 02:48:40 +01:00
editor.cursor . line = editor.cursor . line - 1
2022-05-18 06:36:10 +01:00
end
2024-09-01 02:48:40 +01:00
if editor.screen_top . line > # editor.lines then
editor.screen_top = vert ( editor , editor.cursor , 0 )
elseif edit.lt ( editor.cursor , editor.screen_top ) then
maybe_snap_cursor_to_top_of_screen ( editor )
2022-05-18 06:36:10 +01:00
end
2024-09-01 02:48:40 +01:00
assert ( edit.le ( editor.screen_top , editor.cursor ) , ( ' screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d) ' ) : format ( editor.screen_top . line , editor.screen_top . pos or - 1 , editor.cursor . line , editor.cursor . pos or - 1 ) )
record_undo_event ( editor , { before = before , after = snapshot ( editor , editor.cursor . line ) } )
schedule_save ( editor )
2022-05-18 06:36:10 +01:00
end
2024-06-27 06:35:28 +01:00
elseif chord == ' delete ' then
2024-09-01 02:48:40 +01:00
if editor.cursor . mode == ' drawing ' then
assert ( editor.cursor . line < # editor.lines )
local before = snapshot ( editor , editor.cursor . line , editor.cursor . line + 1 )
table.remove ( editor.lines , editor.cursor . line )
assert ( editor.cursor . line <= # editor.lines )
if editor.lines [ editor.cursor . line ] . mode == ' text ' then
editor.cursor = { mode = ' text ' , line = editor.cursor . line , pos = 1 }
2024-06-22 05:05:51 +01:00
else
2024-09-01 02:48:40 +01:00
editor.cursor = { mode = ' drawing ' , line = editor.cursor . line }
2024-06-22 05:05:51 +01:00
end
2024-07-20 20:16:14 +01:00
-- no need to scroll, but screen_top may have switched mode
2024-09-01 02:48:40 +01:00
if editor.screen_top . line == editor.cursor . line then
editor.screen_top = deepcopy ( editor.cursor )
2024-06-27 06:35:28 +01:00
end
2024-09-01 02:48:40 +01:00
record_undo_event ( editor , { before = before , after = snapshot ( editor , editor.cursor . line ) } )
2022-06-03 01:46:30 +01:00
else
2024-06-27 06:35:28 +01:00
-- cursor in text line
2024-09-01 02:48:40 +01:00
if editor.selection1 . line then
2024-09-01 03:01:33 +01:00
Text.delete_selection_and_record_undo_event ( editor )
2024-09-01 02:48:40 +01:00
schedule_save ( editor )
2024-06-27 06:35:28 +01:00
return
end
local before
2024-09-01 02:48:40 +01:00
if editor.cursor . pos <= utf8.len ( editor.lines [ editor.cursor . line ] . data ) then
before = snapshot ( editor , editor.cursor . line )
2024-06-27 06:35:28 +01:00
else
2024-09-01 02:48:40 +01:00
before = snapshot ( editor , editor.cursor . line , editor.cursor . line + 1 )
2022-05-18 06:36:10 +01:00
end
2024-09-01 02:48:40 +01:00
if editor.cursor . pos <= utf8.len ( editor.lines [ editor.cursor . line ] . data ) then
local byte_start = utf8.offset ( editor.lines [ editor.cursor . line ] . data , editor.cursor . pos )
local byte_end = utf8.offset ( editor.lines [ editor.cursor . line ] . data , editor.cursor . pos + 1 )
2024-06-27 06:35:28 +01:00
if byte_start then
if byte_end then
2024-09-01 02:48:40 +01:00
editor.lines [ editor.cursor . line ] . data = string.sub ( editor.lines [ editor.cursor . line ] . data , 1 , byte_start - 1 ) .. string.sub ( editor.lines [ editor.cursor . line ] . data , byte_end )
2024-06-27 06:35:28 +01:00
else
2024-09-01 02:48:40 +01:00
editor.lines [ editor.cursor . line ] . data = string.sub ( editor.lines [ editor.cursor . line ] . data , 1 , byte_start - 1 )
2024-06-27 06:35:28 +01:00
end
2024-09-01 02:48:40 +01:00
-- no change to editor.cursor.pos
2024-06-27 06:35:28 +01:00
end
2024-09-01 02:48:40 +01:00
elseif editor.cursor . line < # editor.lines then
if editor.lines [ editor.cursor . line + 1 ] . mode == ' text ' then
2024-06-27 06:35:28 +01:00
-- join lines
2024-09-01 02:48:40 +01:00
editor.lines [ editor.cursor . line ] . data = editor.lines [ editor.cursor . line ] . data .. editor.lines [ editor.cursor . line + 1 ] . data
2024-06-27 06:35:28 +01:00
end
2024-09-01 02:48:40 +01:00
table.remove ( editor.lines , editor.cursor . line + 1 )
2022-05-18 06:36:10 +01:00
end
2024-09-01 02:48:40 +01:00
record_undo_event ( editor , { before = before , after = snapshot ( editor , editor.cursor . line ) } )
schedule_save ( editor )
2022-05-18 06:36:10 +01:00
end
2022-05-29 06:27:47 +01:00
--== shortcuts that move the cursor
elseif chord == ' left ' then
2024-09-01 02:48:40 +01:00
Text.left ( editor )
editor.selection1 = { }
2022-06-20 19:35:27 +01:00
elseif chord == ' right ' then
2024-09-01 02:48:40 +01:00
Text.right ( editor )
editor.selection1 = { }
2022-05-29 16:12:47 +01:00
elseif chord == ' S-left ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.left ( editor )
2022-05-29 16:12:47 +01:00
elseif chord == ' S-right ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.right ( editor )
2022-05-29 06:27:47 +01:00
-- C- hotkeys reserved for drawings, so we'll use M-
elseif chord == ' M-left ' then
2024-09-01 02:48:40 +01:00
Text.word_left ( editor )
editor.selection1 = { }
2022-06-20 19:35:27 +01:00
elseif chord == ' M-right ' then
2024-09-01 02:48:40 +01:00
Text.word_right ( editor )
editor.selection1 = { }
2022-05-29 16:12:47 +01:00
elseif chord == ' M-S-left ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.word_left ( editor )
2022-05-29 16:12:47 +01:00
elseif chord == ' M-S-right ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.word_right ( editor )
2022-05-29 06:27:47 +01:00
elseif chord == ' home ' then
2024-09-01 02:48:40 +01:00
Text.start_of_line ( editor )
editor.selection1 = { }
2022-06-20 19:35:27 +01:00
elseif chord == ' end ' then
2024-09-01 02:48:40 +01:00
Text.end_of_line ( editor )
editor.selection1 = { }
2022-05-29 16:12:47 +01:00
elseif chord == ' S-home ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.start_of_line ( editor )
2022-05-29 16:12:47 +01:00
elseif chord == ' S-end ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.end_of_line ( editor )
2022-05-18 06:36:10 +01:00
elseif chord == ' up ' then
2024-09-01 02:48:40 +01:00
Text.up ( editor )
editor.selection1 = { }
2022-06-20 19:35:27 +01:00
elseif chord == ' down ' then
2024-09-01 02:48:40 +01:00
Text.down ( editor )
editor.selection1 = { }
2022-05-29 16:12:47 +01:00
elseif chord == ' S-up ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.up ( editor )
2022-05-29 16:12:47 +01:00
elseif chord == ' S-down ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.down ( editor )
2022-05-29 06:42:11 +01:00
elseif chord == ' pageup ' then
2024-09-01 02:48:40 +01:00
Text.pageup ( editor )
editor.selection1 = { }
2022-06-20 19:35:27 +01:00
elseif chord == ' pagedown ' then
2024-09-01 02:48:40 +01:00
Text.pagedown ( editor )
editor.selection1 = { }
2022-05-29 16:12:47 +01:00
elseif chord == ' S-pageup ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.pageup ( editor )
2022-05-29 16:12:47 +01:00
elseif chord == ' S-pagedown ' then
2024-09-01 02:48:40 +01:00
if editor.selection1 . line == nil then
editor.selection1 = deepcopy ( editor.cursor )
2022-05-29 16:12:47 +01:00
end
2024-09-01 02:48:40 +01:00
Text.pagedown ( editor )
2022-05-29 06:36:05 +01:00
end
end
2024-09-01 02:48:40 +01:00
function Text . insert_return ( editor )
if editor.cursor . mode == ' drawing ' then
table.insert ( editor.lines , editor.cursor . line + 1 , { mode = ' text ' , data = ' ' } )
editor.cursor = { mode = ' text ' , line = editor.cursor . line + 1 , pos = 1 }
2024-06-23 16:26:14 +01:00
return
end
2024-09-01 02:48:40 +01:00
local byte_offset = Text.offset ( editor.lines [ editor.cursor . line ] . data , editor.cursor . pos )
table.insert ( editor.lines , editor.cursor . line + 1 , { mode = ' text ' , data = string.sub ( editor.lines [ editor.cursor . line ] . data , byte_offset ) } )
editor.lines [ editor.cursor . line ] . data = string.sub ( editor.lines [ editor.cursor . line ] . data , 1 , byte_offset - 1 )
editor.cursor = { mode = ' text ' , line = editor.cursor . line + 1 , pos = 1 }
maybe_snap_cursor_to_bottom_of_screen ( editor )
2024-06-11 14:58:07 +01:00
end
2022-06-19 17:03:09 +01:00
function Text . offset ( s , pos1 )
if pos1 == 1 then return 1 end
local result = utf8.offset ( s , pos1 )
if result == nil then
2023-12-09 17:22:45 +00:00
assert ( false , ( ' Text.offset(%d) called on a string of length %d (byte size %d); this is likely a failure to handle utf8 \n \n ^%s$ \n ' ) : format ( pos1 , utf8.len ( s ) , # s , s ) )
2022-06-19 17:03:09 +01:00
end
return result
end