2022-05-18 04:29:08 +01:00
-- primitives for editing text
Text = { }
2022-05-18 06:36:10 +01:00
local utf8 = require ' utf8 '
2022-06-02 23:45:25 +01:00
require ' undo '
2022-05-22 04:52:44 +01:00
-- return values:
-- y coordinate drawn until in px
-- position of start of final screen line drawn
2022-05-20 14:09:37 +01:00
function Text . draw ( line , line_width , line_index )
2022-05-23 19:02:37 +01:00
--? print('text.draw')
2022-05-18 04:29:08 +01:00
love.graphics . setColor ( 0 , 0 , 0 )
2022-05-20 01:29:14 +01:00
-- wrap long lines
local x = 25
local y = line.y
local pos = 1
2022-05-22 04:52:44 +01:00
local screen_line_starting_pos = 1
2022-05-20 01:29:14 +01:00
if line.fragments == nil then
Text.compute_fragments ( line , line_width )
2022-05-18 04:29:08 +01:00
end
2022-05-24 06:31:36 +01:00
if line.screen_line_starting_pos == nil then
Text.populate_screen_line_starting_pos ( line_index )
end
2022-05-24 07:00:07 +01:00
--? print('--')
2022-05-20 01:29:14 +01:00
for _ , f in ipairs ( line.fragments ) do
local frag , frag_text = f.data , f.text
-- render fragment
2022-05-23 07:17:06 +01:00
local frag_width = math.floor ( App.width ( frag_text ) * Zoom )
2022-05-29 16:12:47 +01:00
local frag_len = utf8.len ( frag )
2022-05-29 06:24:17 +01:00
--? local s=tostring
2022-05-24 06:31:36 +01:00
--? print('('..s(x)..','..s(y)..') '..frag..'('..s(frag_width)..' vs '..s(line_width)..') '..s(line_index)..' vs '..s(Screen_top1.line)..'; '..s(pos)..' vs '..s(Screen_top1.pos)..'; bottom: '..s(Screen_bottom1.line)..'/'..s(Screen_bottom1.pos))
2022-05-20 01:29:14 +01:00
if x + frag_width > line_width then
assert ( x > 25 ) -- no overfull lines
2022-05-24 00:35:25 +01:00
-- update y only after drawing the first screen line of screen top
2022-05-29 07:11:53 +01:00
if Text.lt1 ( Screen_top1 , { line = line_index , pos = pos } ) then
2022-05-20 14:58:17 +01:00
y = y + math.floor ( 15 * Zoom )
2022-05-23 23:34:05 +01:00
if y + math.floor ( 15 * Zoom ) > App.screen . height then
2022-05-24 06:31:36 +01:00
--? print('b', y, App.screen.height, '=>', screen_line_starting_pos)
2022-05-22 06:08:13 +01:00
return y , screen_line_starting_pos
end
2022-05-24 06:31:36 +01:00
screen_line_starting_pos = pos
--? print('text: new screen line', y, App.screen.height, screen_line_starting_pos)
2022-05-22 06:08:13 +01:00
end
2022-05-24 06:31:36 +01:00
x = 25
2022-05-20 01:29:14 +01:00
end
2022-05-24 06:31:36 +01:00
--? print('checking to draw', pos, Screen_top1.pos)
2022-05-23 23:34:05 +01:00
-- don't draw text above screen top
2022-05-29 07:11:53 +01:00
if Text.le1 ( Screen_top1 , { line = line_index , pos = pos } ) then
2022-05-29 16:12:47 +01:00
if Selection1.line then
local lo , hi = Text.clip_selection ( line_index , pos , pos + frag_len )
if lo then
local lo_offset = utf8.offset ( line.data , lo )
local hi_offset = utf8.offset ( line.data , hi )
local pos_offset = utf8.offset ( line.data , pos )
local lo_px
if pos == lo then
lo_px = 0
else
local before = line.data : sub ( pos_offset , lo_offset - 1 )
local before_text = App.newText ( love.graphics . getFont ( ) , before )
lo_px = App.width ( before_text ) * Zoom
end
--? print(lo,pos,hi, '--', lo_offset,pos_offset,hi_offset, '--', lo_px)
local s = line.data : sub ( lo_offset , hi_offset - 1 )
local text = App.newText ( love.graphics . getFont ( ) , s )
local text_width = App.width ( text ) * Zoom
love.graphics . setColor ( 0.7 , 0.7 , 0.9 )
love.graphics . rectangle ( ' fill ' , x + lo_px , y , text_width , math.floor ( 15 * Zoom ) )
love.graphics . setColor ( 0 , 0 , 0 )
end
end
2022-05-24 07:00:07 +01:00
--? print('drawing '..frag)
2022-05-23 07:17:06 +01:00
App.screen . draw ( frag_text , x , y , 0 , Zoom )
2022-05-20 14:58:17 +01:00
end
2022-05-20 01:29:14 +01:00
-- render cursor if necessary
2022-05-22 00:24:07 +01:00
if line_index == Cursor1.line then
if pos <= Cursor1.pos and pos + frag_len > Cursor1.pos then
2022-05-25 23:20:18 +01:00
Text.draw_cursor ( x + Text.x ( frag , Cursor1.pos - pos + 1 ) , y )
2022-05-20 01:29:14 +01:00
end
end
x = x + frag_width
pos = pos + frag_len
end
2022-05-22 00:24:07 +01:00
if line_index == Cursor1.line and Cursor1.pos == pos then
2022-05-20 13:40:42 +01:00
Text.draw_cursor ( x , y )
2022-05-20 06:35:26 +01:00
end
2022-05-22 04:52:44 +01:00
return y , screen_line_starting_pos
2022-05-18 04:29:08 +01:00
end
2022-05-20 01:29:14 +01:00
-- manual tests:
-- draw with small line_width of 100
2022-05-29 16:12:47 +01:00
-- Return any intersection of the region from Selection1 to Cursor1 with the
-- region between {line=line_index, pos=apos} and {line=line_index, pos=bpos}.
-- apos must be less than bpos. However Selection1 and Cursor1 can be in any order.
-- Result: positions spos,epos between apos,bpos.
function Text . clip_selection ( line_index , apos , bpos )
if Selection1.line == nil then return nil , nil end
-- min,max = sorted(Selection1,Cursor1)
local minl , minp = Selection1.line , Selection1.pos
local maxl , maxp = Cursor1.line , Cursor1.pos
if minl > maxl then
minl , maxl = maxl , minl
minp , maxp = maxp , minp
elseif minl == maxl then
if minp > maxp then
minp , maxp = maxp , minp
end
end
-- check if intervals are disjoint
if line_index < minl then return nil , nil end
if line_index > maxl then return nil , nil end
if line_index == minl and bpos <= minp then return nil , nil end
if line_index == maxl and apos >= maxp then return nil , nil end
-- compare bounds more carefully (start inclusive, end exclusive)
local a_ge = Text.le1 ( { line = minl , pos = minp } , { line = line_index , pos = apos } )
local b_lt = Text.lt1 ( { line = line_index , pos = bpos } , { line = maxl , pos = maxp } )
--? print(minl,line_index,maxl, '--', minp,apos,bpos,maxp, '--', a_ge,b_lt)
if a_ge and b_lt then
-- fully contained
return apos , bpos
elseif a_ge then
assert ( maxl == line_index )
return apos , maxp
elseif b_lt then
assert ( minl == line_index )
return minp , bpos
else
assert ( minl == maxl and minl == line_index )
return minp , maxp
end
end
function Text . delete_selection ( )
if Selection1.line == nil then return end
-- min,max = sorted(Selection1,Cursor1)
local minl , minp = Selection1.line , Selection1.pos
local maxl , maxp = Cursor1.line , Cursor1.pos
if minl > maxl then
minl , maxl = maxl , minl
minp , maxp = maxp , minp
elseif minl == maxl then
if minp > maxp then
minp , maxp = maxp , minp
end
end
-- update Cursor1 and Selection1
Cursor1.line = minl
Cursor1.pos = minp
Selection1 = { }
-- delete everything between min (inclusive) and max (exclusive)
Lines [ minl ] . fragments = nil
Lines [ minl ] . screen_line_starting_pos = nil
local min_offset = utf8.offset ( Lines [ minl ] . data , minp )
local max_offset = utf8.offset ( Lines [ maxl ] . data , maxp )
if minl == maxl then
--? print('minl == maxl')
Lines [ minl ] . data = Lines [ minl ] . data : sub ( 1 , min_offset - 1 ) .. Lines [ minl ] . data : sub ( max_offset )
return
end
assert ( minl < maxl )
local rhs = Lines [ maxl ] . data : sub ( max_offset )
for i = maxl , minl + 1 , - 1 do
table.remove ( Lines , i )
end
Lines [ minl ] . data = Lines [ minl ] . data : sub ( 1 , min_offset - 1 ) .. rhs
end
2022-05-18 04:29:08 +01:00
2022-05-29 23:33:24 +01:00
function Text . selection ( )
if Selection1.line == nil then return end
-- min,max = sorted(Selection1,Cursor1)
local minl , minp = Selection1.line , Selection1.pos
local maxl , maxp = Cursor1.line , Cursor1.pos
if minl > maxl then
minl , maxl = maxl , minl
minp , maxp = maxp , minp
elseif minl == maxl then
if minp > maxp then
minp , maxp = maxp , minp
end
end
local min_offset = utf8.offset ( Lines [ minl ] . data , minp )
local max_offset = utf8.offset ( Lines [ maxl ] . data , maxp )
if minl == maxl then
return Lines [ minl ] . data : sub ( min_offset , max_offset - 1 )
end
assert ( minl < maxl )
local result = Lines [ minl ] . data : sub ( min_offset ) .. ' \n '
for i = minl + 1 , maxl - 1 do
if Lines [ i ] . mode == ' text ' then
result = result .. Lines [ i ] . data .. ' \n '
end
end
result = result .. Lines [ maxl ] . data : sub ( 1 , max_offset - 1 )
return result
end
function Text . cut_selection ( )
local result = Text.selection ( )
Text.delete_selection ( )
return result
end
2022-05-20 13:40:42 +01:00
function Text . draw_cursor ( x , y )
love.graphics . setColor ( 1 , 0 , 0 )
love.graphics . circle ( ' fill ' , x , y + math.floor ( 15 * Zoom ) , 2 )
love.graphics . setColor ( 0 , 0 , 0 )
2022-05-20 13:41:25 +01:00
Cursor_x = x
Cursor_y = y + math.floor ( 15 * Zoom )
2022-05-20 13:40:42 +01:00
end
2022-05-23 07:17:06 +01:00
function test_draw_text ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_draw_text ' )
2022-05-23 07:17:06 +01:00
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 07:17:06 +01:00
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_draw_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_draw_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_draw_text/screen:3 ' )
end
2022-05-25 20:59:12 +01:00
function test_draw_wrapping_text ( )
io.write ( ' \n test_draw_wrapping_text ' )
App.screen . init { width = 50 , height = 60 }
Lines = load_array { ' abc ' , ' defgh ' , ' xyz ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_draw_wrapping_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_draw_wrapping_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' gh ' , ' F - test_draw_wrapping_text/screen:3 ' )
end
2022-05-28 17:02:15 +01:00
function test_draw_word_wrapping_text ( )
io.write ( ' \n test_draw_word_wrapping_text ' )
App.screen . init { width = 60 , height = 60 }
Lines = load_array { ' abc def ghi ' , ' jkl ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_draw_word_wrapping_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_draw_word_wrapping_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_draw_word_wrapping_text/screen:3 ' )
end
function test_draw_text_wrapping_within_word ( )
-- arrange a screen line that needs to be split within a word
io.write ( ' \n test_draw_text_wrapping_within_word ' )
App.screen . init { width = 60 , height = 60 }
Lines = load_array { ' abcd e fghijk ' , ' xyz ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abcd ' , ' F - test_draw_text_wrapping_within_word/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' e fghi ' , ' F - test_draw_text_wrapping_within_word/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jk ' , ' F - test_draw_text_wrapping_within_word/screen:3 ' )
end
2022-05-25 20:59:12 +01:00
function test_edit_wrapping_text ( )
io.write ( ' \n test_edit_wrapping_text ' )
App.screen . init { width = 50 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' xyz ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 4 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.run_after_textinput ( ' g ' )
App.run_after_textinput ( ' h ' )
App.run_after_textinput ( ' i ' )
App.run_after_textinput ( ' j ' )
App.run_after_textinput ( ' k ' )
App.run_after_textinput ( ' l ' )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_edit_wrapping_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_edit_wrapping_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghij ' , ' F - test_edit_wrapping_text/screen:3 ' )
end
2022-05-25 21:38:33 +01:00
function test_move_cursor_using_mouse ( )
io.write ( ' \n test_move_cursor_using_mouse ' )
App.screen . init { width = 50 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' xyz ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( ) -- populate line.y for each line in Lines
local screen_top_margin = 15 -- pixels
local screen_left_margin = 25 -- pixels
App.run_after_mousepress ( screen_left_margin + 8 , screen_top_margin + 5 , ' 1 ' )
check_eq ( Cursor1.line , 1 , ' F - test_move_cursor_using_mouse/cursor:line ' )
check_eq ( Cursor1.pos , 2 , ' F - test_move_cursor_using_mouse/cursor:pos ' )
end
2022-05-23 16:13:58 +01:00
function test_pagedown ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_pagedown ' )
2022-05-23 16:13:58 +01:00
App.screen . init { width = 120 , height = 45 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 16:13:58 +01:00
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
-- initially the first two lines are displayed
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_pagedown/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_pagedown/baseline/screen:2 ' )
-- after pagedown the bottom line becomes the top
App.run_after_keychord ( ' pagedown ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 2 , ' F - test_pagedown/screen_top ' )
check_eq ( Cursor1.line , 2 , ' F - test_pagedown/cursor ' )
2022-05-23 16:13:58 +01:00
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_pagedown/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_pagedown/screen:2 ' )
end
2022-05-23 18:56:39 +01:00
function test_pagedown_skips_drawings ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_pagedown_skips_drawings ' )
2022-05-23 16:13:58 +01:00
-- some lines of text with a drawing intermixed
2022-05-23 16:52:13 +01:00
App.screen . init { width = 50 , height = 80 }
Lines = load_array { ' abc ' , -- height 15
' ```lines ' , ' ``` ' , -- height 25
' def ' , -- height 15
' ghi ' } -- height 15
2022-05-23 18:56:39 +01:00
check_eq ( Lines [ 2 ] . mode , ' drawing ' , ' F - test_pagedown_skips_drawings/baseline/lines ' )
2022-05-23 16:13:58 +01:00
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
2022-05-23 16:52:13 +01:00
local drawing_height = 20 + App.screen . width / 2 -- default
-- initially the screen displays the first line and the drawing
-- 15px margin + 15px line1 + 10px margin + 25px drawing + 10px margin = 75px < screen height 80px
2022-05-23 16:13:58 +01:00
App.draw ( )
local y = screen_top_margin
2022-05-23 18:56:39 +01:00
App.screen . check ( y , ' abc ' , ' F - test_pagedown_skips_drawings/baseline/screen:1 ' )
2022-05-23 23:22:16 +01:00
-- after pagedown the screen draws the drawing up top
2022-05-23 16:52:13 +01:00
-- 15px margin + 10px margin + 25px drawing + 10px margin + 15px line3 = 75px < screen height 80px
2022-05-23 16:13:58 +01:00
App.run_after_keychord ( ' pagedown ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 2 , ' F - test_pagedown_skips_drawings/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_pagedown_skips_drawings/cursor ' )
2022-05-23 16:13:58 +01:00
y = screen_top_margin + drawing_height
2022-05-23 18:56:39 +01:00
App.screen . check ( y , ' def ' , ' F - test_pagedown_skips_drawings/screen:1 ' )
end
2022-05-24 06:31:36 +01:00
function test_pagedown_shows_one_screen_line_in_common ( )
io.write ( ' \n test_pagedown_shows_one_screen_line_in_common ' )
-- some lines of text with a drawing intermixed
App.screen . init { width = 50 , height = 60 }
Lines = load_array { ' abc ' , ' def ghi jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_pagedown_shows_one_screen_line_in_common/baseline/screen:3 ' )
-- after pagedown the bottom screen line becomes the top
App.run_after_keychord ( ' pagedown ' )
check_eq ( Screen_top1.line , 2 , ' F - test_pagedown_shows_one_screen_line_in_common/screen_top:line ' )
check_eq ( Screen_top1.pos , 5 , ' F - test_pagedown_shows_one_screen_line_in_common/screen_top:pos ' )
check_eq ( Cursor1.line , 2 , ' F - test_pagedown_shows_one_screen_line_in_common/cursor:line ' )
check_eq ( Cursor1.pos , 5 , ' F - test_pagedown_shows_one_screen_line_in_common/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' ghi ' , ' F - test_pagedown_shows_one_screen_line_in_common/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_pagedown_shows_one_screen_line_in_common/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' mn ' , ' F - test_pagedown_shows_one_screen_line_in_common/screen:3 ' )
end
2022-05-23 18:56:39 +01:00
function test_down_arrow_moves_cursor ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_down_arrow_moves_cursor ' )
2022-05-23 18:56:39 +01:00
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 18:56:39 +01:00
Cursor1 = { line = 1 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
-- initially the first three lines are displayed
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_moves_cursor/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_moves_cursor/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_moves_cursor/baseline/screen:3 ' )
2022-05-23 23:22:16 +01:00
-- after hitting the down arrow, the cursor moves down by 1 line
2022-05-23 18:56:39 +01:00
App.run_after_keychord ( ' down ' )
2022-05-24 22:34:50 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_down_arrow_moves_cursor/screen_top ' )
2022-05-23 23:22:16 +01:00
check_eq ( Cursor1.line , 2 , ' F - test_down_arrow_moves_cursor/cursor ' )
-- the screen is unchanged
2022-05-23 18:56:39 +01:00
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_moves_cursor/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_moves_cursor/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_moves_cursor/screen:3 ' )
end
function test_down_arrow_scrolls_down_by_one_line ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_down_arrow_scrolls_down_by_one_line ' )
2022-05-23 18:56:39 +01:00
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-23 18:56:39 +01:00
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_line/baseline/screen:3 ' )
-- after hitting the down arrow the screen scrolls down by one line
App.run_after_keychord ( ' down ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 2 , ' F - test_down_arrow_scrolls_down_by_one_line/screen_top ' )
check_eq ( Cursor1.line , 4 , ' F - test_down_arrow_scrolls_down_by_one_line/cursor ' )
2022-05-23 18:56:39 +01:00
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_down_arrow_scrolls_down_by_one_line/screen:3 ' )
2022-05-23 23:22:16 +01:00
end
function test_down_arrow_scrolls_down_by_one_screen_line ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_down_arrow_scrolls_down_by_one_screen_line ' )
2022-05-23 23:22:16 +01:00
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/baseline/screen:3 ' ) -- line wrapping includes trailing whitespace
-- after hitting the down arrow the screen scrolls down by one line
App.run_after_keychord ( ' down ' )
check_eq ( Screen_top1.line , 2 , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 5 , ' F - test_down_arrow_scrolls_down_by_one_screen_line/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line/screen:3 ' )
end
function test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word ' )
2022-05-23 23:22:16 +01:00
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghijkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghijk ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/baseline/screen:3 ' )
-- after hitting the down arrow the screen scrolls down by one line
App.run_after_keychord ( ' down ' )
check_eq ( Screen_top1.line , 2 , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:line ' )
check_eq ( Cursor1.pos , 6 , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghijk ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' l ' , ' F - test_down_arrow_scrolls_down_by_one_screen_line_after_splitting_within_word/screen:3 ' )
2022-05-23 19:02:37 +01:00
end
2022-05-24 23:01:09 +01:00
function test_page_down_followed_by_down_arrow_does_not_scroll_screen_up ( )
io.write ( ' \n test_page_down_followed_by_down_arrow_does_not_scroll_screen_up ' )
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghijkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghijk ' , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline/screen:3 ' )
-- after hitting pagedown the screen scrolls down to start of a long line
App.run_after_keychord ( ' pagedown ' )
check_eq ( Screen_top1.line , 3 , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:line ' )
check_eq ( Cursor1.pos , 1 , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/baseline2/cursor:pos ' )
-- after hitting down arrow the screen doesn't scroll down further, and certainly doesn't scroll up
App.run_after_keychord ( ' down ' )
check_eq ( Screen_top1.line , 3 , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:line ' )
check_eq ( Cursor1.pos , 6 , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' ghijk ' , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' l ' , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' mno ' , ' F - test_page_down_followed_by_down_arrow_does_not_scroll_screen_up/screen:3 ' )
end
2022-05-23 19:02:37 +01:00
function test_up_arrow_moves_cursor ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_up_arrow_moves_cursor ' )
2022-05-23 19:02:37 +01:00
-- display the first 3 lines with the cursor on the bottom line
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = 120
Cursor1 = { line = 3 , pos = 1 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_moves_cursor/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_moves_cursor/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_moves_cursor/baseline/screen:3 ' )
2022-05-23 23:22:16 +01:00
-- after hitting the up arrow the cursor moves up by 1 line
2022-05-23 19:02:37 +01:00
App.run_after_keychord ( ' up ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_moves_cursor/screen_top ' )
check_eq ( Cursor1.line , 2 , ' F - test_up_arrow_moves_cursor/cursor ' )
-- the screen is unchanged
2022-05-23 19:02:37 +01:00
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_moves_cursor/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_moves_cursor/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_moves_cursor/screen:3 ' )
end
function test_up_arrow_scrolls_up_by_one_line ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_up_arrow_scrolls_up_by_one_line ' )
2022-05-23 19:02:37 +01:00
-- display the lines 2/3/4 with the cursor on line 2
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = 120
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_by_one_line/baseline/screen:3 ' )
-- after hitting the up arrow the screen scrolls up by one line
App.run_after_keychord ( ' up ' )
2022-05-23 23:22:16 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_scrolls_up_by_one_line/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_up_arrow_scrolls_up_by_one_line/cursor ' )
2022-05-23 19:02:37 +01:00
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_scrolls_up_by_one_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_by_one_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_line/screen:3 ' )
2022-05-23 23:22:16 +01:00
end
function test_up_arrow_scrolls_up_by_one_screen_line ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_up_arrow_scrolls_up_by_one_screen_line ' )
2022-05-24 01:39:00 +01:00
-- display lines starting from second screen line of a line
2022-05-24 00:35:25 +01:00
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi jkl ' , ' mno ' }
2022-05-23 23:22:16 +01:00
Line_width = App.screen . width
2022-05-24 00:35:25 +01:00
Cursor1 = { line = 3 , pos = 6 }
Screen_top1 = { line = 3 , pos = 5 }
2022-05-23 23:22:16 +01:00
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
2022-05-24 00:35:25 +01:00
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:1 ' )
2022-05-23 23:22:16 +01:00
y = y + line_height
2022-05-24 00:35:25 +01:00
App.screen . check ( y , ' mno ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/baseline/screen:2 ' )
2022-05-24 01:39:00 +01:00
-- after hitting the up arrow the screen scrolls up to first screen line
2022-05-23 23:22:16 +01:00
App.run_after_keychord ( ' up ' )
y = screen_top_margin
2022-05-24 00:35:25 +01:00
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen:1 ' )
2022-05-23 23:22:16 +01:00
y = y + line_height
2022-05-24 00:35:25 +01:00
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen:2 ' )
2022-05-23 23:22:16 +01:00
y = y + line_height
2022-05-24 00:35:25 +01:00
App.screen . check ( y , ' mno ' , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen:3 ' )
check_eq ( Screen_top1.line , 3 , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top ' )
check_eq ( Screen_top1.pos , 1 , ' F - test_up_arrow_scrolls_up_by_one_screen_line/screen_top ' )
2022-05-28 17:35:06 +01:00
check_eq ( Cursor1.line , 3 , ' F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 1 , ' F - test_up_arrow_scrolls_up_by_one_screen_line/cursor:pos ' )
2022-05-23 16:13:58 +01:00
end
2022-05-24 01:39:00 +01:00
function test_up_arrow_scrolls_up_to_final_screen_line ( )
2022-05-24 01:40:44 +01:00
io.write ( ' \n test_up_arrow_scrolls_up_to_final_screen_line ' )
2022-05-24 01:39:00 +01:00
-- display lines starting just after a long line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' mno ' , ' F - test_up_arrow_scrolls_up_to_final_screen_line/baseline/screen:3 ' )
-- after hitting the up arrow the screen scrolls up to final screen line of previous line
App.run_after_keychord ( ' up ' )
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_to_final_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_to_final_screen_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_up_arrow_scrolls_up_to_final_screen_line/screen:3 ' )
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top ' )
check_eq ( Screen_top1.pos , 5 , ' F - test_up_arrow_scrolls_up_to_final_screen_line/screen_top ' )
2022-05-28 17:35:06 +01:00
check_eq ( Cursor1.line , 1 , ' F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 5 , ' F - test_up_arrow_scrolls_up_to_final_screen_line/cursor:pos ' )
2022-05-24 01:39:00 +01:00
end
2022-05-24 22:34:50 +01:00
function test_up_arrow_scrolls_up_to_empty_line ( )
2022-05-28 17:31:51 +01:00
io.write ( ' \n test_up_arrow_scrolls_up_to_empty_line ' )
2022-05-24 22:34:50 +01:00
-- display a screenful of text with an empty line just above it outside the screen
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' ' , ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = 120
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
2022-05-28 17:31:51 +01:00
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:1 ' )
2022-05-24 22:34:50 +01:00
y = y + line_height
2022-05-28 17:31:51 +01:00
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:2 ' )
2022-05-24 22:34:50 +01:00
y = y + line_height
2022-05-28 17:31:51 +01:00
App.screen . check ( y , ' ghi ' , ' F - test_up_arrow_scrolls_up_to_empty_line/baseline/screen:3 ' )
2022-05-24 22:34:50 +01:00
-- after hitting the up arrow the screen scrolls up by one line
App.run_after_keychord ( ' up ' )
2022-05-28 17:31:51 +01:00
check_eq ( Screen_top1.line , 1 , ' F - test_up_arrow_scrolls_up_to_empty_line/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_up_arrow_scrolls_up_to_empty_line/cursor ' )
2022-05-24 22:34:50 +01:00
y = screen_top_margin
-- empty first line
y = y + line_height
2022-05-28 17:31:51 +01:00
App.screen . check ( y , ' abc ' , ' F - test_up_arrow_scrolls_up_to_empty_line/screen:2 ' )
2022-05-24 22:34:50 +01:00
y = y + line_height
2022-05-28 17:31:51 +01:00
App.screen . check ( y , ' def ' , ' F - test_up_arrow_scrolls_up_to_empty_line/screen:3 ' )
2022-05-24 22:34:50 +01:00
end
2022-05-24 04:57:56 +01:00
function test_pageup ( )
io.write ( ' \n test_pageup ' )
App.screen . init { width = 120 , height = 45 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
-- initially the last two lines are displayed
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_pageup/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_pageup/baseline/screen:2 ' )
-- after pageup the cursor goes to first line
App.run_after_keychord ( ' pageup ' )
check_eq ( Screen_top1.line , 1 , ' F - test_pageup/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_pageup/cursor ' )
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_pageup/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_pageup/screen:2 ' )
end
function test_pageup_scrolls_up_by_screen_line ( )
io.write ( ' \n test_pageup_scrolls_up_by_screen_line ' )
-- display the first three lines with the cursor on the bottom line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' ghi ' , ' F - test_pageup_scrolls_up_by_screen_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_pageup_scrolls_up_by_screen_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' mno ' , ' F - test_pageup_scrolls_up_by_screen_line/baseline/screen:3 ' ) -- line wrapping includes trailing whitespace
-- after hitting the page-up key the screen scrolls up to top
App.run_after_keychord ( ' pageup ' )
check_eq ( Screen_top1.line , 1 , ' F - test_pageup_scrolls_up_by_screen_line/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_pageup_scrolls_up_by_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 1 , ' F - test_pageup_scrolls_up_by_screen_line/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_pageup_scrolls_up_by_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_pageup_scrolls_up_by_screen_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_pageup_scrolls_up_by_screen_line/screen:3 ' )
end
function test_pageup_scrolls_up_from_middle_screen_line ( )
io.write ( ' \n test_pageup_scrolls_up_from_middle_screen_line ' )
-- display a few lines starting from the middle of a line (Cursor1.pos > 1)
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc def ' , ' ghi jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 5 }
Screen_top1 = { line = 2 , pos = 5 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' jkl ' , ' F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' mno ' , ' F - test_pageup_scrolls_up_from_middle_screen_line/baseline/screen:3 ' ) -- line wrapping includes trailing whitespace
-- after hitting the page-up key the screen scrolls up to top
App.run_after_keychord ( ' pageup ' )
check_eq ( Screen_top1.line , 1 , ' F - test_pageup_scrolls_up_from_middle_screen_line/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_pageup_scrolls_up_from_middle_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 1 , ' F - test_pageup_scrolls_up_from_middle_screen_line/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_pageup_scrolls_up_from_middle_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_pageup_scrolls_up_from_middle_screen_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_pageup_scrolls_up_from_middle_screen_line/screen:3 ' )
end
2022-05-28 16:38:49 +01:00
function test_enter_on_bottom_line_scrolls_down ( )
io.write ( ' \n test_enter_on_bottom_line_scrolls_down ' )
-- display a few lines with cursor on bottom line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 2 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_enter_on_bottom_line_scrolls_down/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_enter_on_bottom_line_scrolls_down/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_enter_on_bottom_line_scrolls_down/baseline/screen:3 ' )
-- after hitting the enter key the screen scrolls down
App.run_after_keychord ( ' return ' )
check_eq ( Screen_top1.line , 2 , ' F - test_enter_on_bottom_line_scrolls_down/screen_top ' )
check_eq ( Cursor1.line , 4 , ' F - test_enter_on_bottom_line_scrolls_down/cursor:line ' )
check_eq ( Cursor1.pos , 1 , ' F - test_enter_on_bottom_line_scrolls_down/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_enter_on_bottom_line_scrolls_down/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' g ' , ' F - test_enter_on_bottom_line_scrolls_down/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' hi ' , ' F - test_enter_on_bottom_line_scrolls_down/screen:3 ' )
end
2022-05-31 01:24:23 +01:00
function test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom ( )
io.write ( ' \n test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom ' )
-- display just the bottom line on screen
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = App.screen . width
Cursor1 = { line = 4 , pos = 2 }
Screen_top1 = { line = 4 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' jkl ' , ' F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/baseline/screen:1 ' )
-- after hitting the enter key the screen does not scroll down
App.run_after_keychord ( ' return ' )
check_eq ( Screen_top1.line , 4 , ' F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen_top ' )
check_eq ( Cursor1.line , 5 , ' F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:line ' )
check_eq ( Cursor1.pos , 1 , ' F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' j ' , ' F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' kl ' , ' F - test_enter_on_final_line_avoids_scrolling_down_when_not_at_bottom/screen:2 ' )
end
2022-05-25 22:31:16 +01:00
function test_position_cursor_on_recently_edited_wrapping_line ( )
-- draw a line wrapping over 2 screen lines
io.write ( ' \n test_position_cursor_on_recently_edited_wrapping_line ' )
App.screen . init { width = 120 , height = 200 }
Lines = load_array { ' abc def ghi jkl mno pqr ' , ' xyz ' }
Line_width = 100
Cursor1 = { line = 1 , pos = 25 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
App.draw ( )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
-- I don't understand why 120px fits so much on a fake screen, but whatever..
App.screen . check ( y , ' abc def ghi ' , ' F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' jkl mno pqr ' , ' F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' xyz ' , ' F - test_position_cursor_on_recently_edited_wrapping_line/baseline1/screen:3 ' )
-- add to the line until it's wrapping over 3 screen lines
App.run_after_textinput ( ' s ' )
App.run_after_textinput ( ' t ' )
App.run_after_textinput ( ' u ' )
check_eq ( Cursor1.pos , 28 , ' F - test_move_cursor_using_mouse/cursor:pos ' )
y = screen_top_margin
App.screen . check ( y , ' abc def ghi ' , ' F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' jkl mno pqr ' , ' F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' stu ' , ' F - test_position_cursor_on_recently_edited_wrapping_line/baseline2/screen:3 ' )
-- try to move the cursor earlier in the third screen line by clicking the mouse
local screen_top_margin = 15 -- pixels
local screen_left_margin = 25 -- pixels
App.run_after_mousepress ( screen_left_margin + 8 , screen_top_margin + 15 * 2 + 5 , ' 1 ' )
-- cursor should move
check_eq ( Cursor1.line , 1 , ' F - test_move_cursor_using_mouse/cursor:line ' )
check_eq ( Cursor1.pos , 26 , ' F - test_move_cursor_using_mouse/cursor:pos ' )
end
2022-05-28 17:41:02 +01:00
function test_backspace_can_scroll_up ( )
io.write ( ' \n test_backspace_can_scroll_up ' )
-- display the lines 2/3/4 with the cursor on line 2
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' }
Line_width = 120
Cursor1 = { line = 2 , pos = 1 }
Screen_top1 = { line = 2 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' def ' , ' F - test_backspace_can_scroll_up/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_backspace_can_scroll_up/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_backspace_can_scroll_up/baseline/screen:3 ' )
-- after hitting backspace the screen scrolls up by one line
App.run_after_keychord ( ' backspace ' )
check_eq ( Screen_top1.line , 1 , ' F - test_backspace_can_scroll_up/screen_top ' )
check_eq ( Cursor1.line , 1 , ' F - test_backspace_can_scroll_up/cursor ' )
y = screen_top_margin
App.screen . check ( y , ' abcdef ' , ' F - test_backspace_can_scroll_up/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' ghi ' , ' F - test_backspace_can_scroll_up/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' jkl ' , ' F - test_backspace_can_scroll_up/screen:3 ' )
end
function test_backspace_can_scroll_up_screen_line ( )
io.write ( ' \n test_backspace_can_scroll_up_screen_line ' )
-- display lines starting from second screen line of a line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 3 , pos = 5 }
Screen_top1 = { line = 3 , pos = 5 }
Screen_bottom1 = { }
Zoom = 1
local screen_top_margin = 15 -- pixels
local line_height = math.floor ( 15 * Zoom ) -- pixels
App.draw ( )
local y = screen_top_margin
App.screen . check ( y , ' jkl ' , ' F - test_backspace_can_scroll_up_screen_line/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' mno ' , ' F - test_backspace_can_scroll_up_screen_line/baseline/screen:2 ' )
-- after hitting backspace the screen scrolls up by one screen line
App.run_after_keychord ( ' backspace ' )
y = screen_top_margin
App.screen . check ( y , ' ghijk ' , ' F - test_backspace_can_scroll_up_screen_line/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' l ' , ' F - test_backspace_can_scroll_up_screen_line/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' mno ' , ' F - test_backspace_can_scroll_up_screen_line/screen:3 ' )
check_eq ( Screen_top1.line , 3 , ' F - test_backspace_can_scroll_up_screen_line/screen_top ' )
check_eq ( Screen_top1.pos , 1 , ' F - test_backspace_can_scroll_up_screen_line/screen_top ' )
check_eq ( Cursor1.line , 3 , ' F - test_backspace_can_scroll_up_screen_line/cursor:line ' )
check_eq ( Cursor1.pos , 4 , ' F - test_backspace_can_scroll_up_screen_line/cursor:pos ' )
end
2022-05-29 16:12:47 +01:00
-- some tests for operating over selections created using Shift- chords
-- we're just testing delete_selection, and it works the same for all keys
function test_backspace_over_selection ( )
io.write ( ' \n test_backspace_over_selection ' )
-- select just one character within a line with cursor before selection
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 1 }
Selection1 = { line = 1 , pos = 2 }
Zoom = 1
-- backspace deletes the selected character, even though it's after the cursor
App.run_after_keychord ( ' backspace ' )
check_eq ( Lines [ 1 ] . data , ' bc ' , " F - test_backspace_over_selection/data " )
-- cursor (remains) at start of selection
check_eq ( Cursor1.line , 1 , " F - test_backspace_over_selection/cursor:line " )
check_eq ( Cursor1.pos , 1 , " F - test_backspace_over_selection/cursor:pos " )
-- selection is cleared
check_nil ( Selection1.line , " F - test_backspace_over_selection/selection " )
end
function test_backspace_over_selection_reverse ( )
2022-05-30 00:03:02 +01:00
io.write ( ' \n test_backspace_over_selection_reverse ' )
2022-05-29 16:12:47 +01:00
-- select just one character within a line with cursor after selection
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 2 }
Selection1 = { line = 1 , pos = 1 }
Zoom = 1
-- backspace deletes the selected character
App.run_after_keychord ( ' backspace ' )
check_eq ( Lines [ 1 ] . data , ' bc ' , " F - test_backspace_over_selection_reverse/data " )
-- cursor moves to start of selection
check_eq ( Cursor1.line , 1 , " F - test_backspace_over_selection_reverse/cursor:line " )
check_eq ( Cursor1.pos , 1 , " F - test_backspace_over_selection_reverse/cursor:pos " )
-- selection is cleared
check_nil ( Selection1.line , " F - test_backspace_over_selection_reverse/selection " )
end
function test_backspace_over_multiple_lines ( )
2022-05-30 00:03:02 +01:00
io.write ( ' \n test_backspace_over_multiple_lines ' )
2022-05-29 16:12:47 +01:00
-- select just one character within a line with cursor after selection
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 2 }
Selection1 = { line = 4 , pos = 2 }
Zoom = 1
-- backspace deletes the region and joins the remaining portions of lines on either side
App.run_after_keychord ( ' backspace ' )
check_eq ( Lines [ 1 ] . data , ' akl ' , " F - test_backspace_over_multiple_lines/data:1 " )
check_eq ( Lines [ 2 ] . data , ' mno ' , " F - test_backspace_over_multiple_lines/data:2 " )
-- cursor remains at start of selection
check_eq ( Cursor1.line , 1 , " F - test_backspace_over_multiple_lines/cursor:line " )
check_eq ( Cursor1.pos , 2 , " F - test_backspace_over_multiple_lines/cursor:pos " )
-- selection is cleared
check_nil ( Selection1.line , " F - test_backspace_over_multiple_lines/selection " )
end
function test_backspace_to_end_of_line ( )
2022-05-30 00:03:02 +01:00
io.write ( ' \n test_backspace_to_end_of_line ' )
2022-05-29 16:12:47 +01:00
-- select region from cursor to end of line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 1 , pos = 2 }
Selection1 = { line = 1 , pos = 4 }
Zoom = 1
-- backspace deletes rest of line without joining to any other line
App.run_after_keychord ( ' backspace ' )
check_eq ( Lines [ 1 ] . data , ' a ' , " F - test_backspace_to_start_of_line/data:1 " )
check_eq ( Lines [ 2 ] . data , ' def ' , " F - test_backspace_to_start_of_line/data:2 " )
-- cursor remains at start of selection
check_eq ( Cursor1.line , 1 , " F - test_backspace_to_start_of_line/cursor:line " )
check_eq ( Cursor1.pos , 2 , " F - test_backspace_to_start_of_line/cursor:pos " )
-- selection is cleared
check_nil ( Selection1.line , " F - test_backspace_to_start_of_line/selection " )
end
function test_backspace_to_start_of_line ( )
2022-05-30 00:03:02 +01:00
io.write ( ' \n test_backspace_to_start_of_line ' )
2022-05-29 16:12:47 +01:00
-- select region from cursor to start of line
App.screen . init { width = 25 + 30 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' ghi ' , ' jkl ' , ' mno ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 1 }
Selection1 = { line = 2 , pos = 3 }
Zoom = 1
-- backspace deletes beginning of line without joining to any other line
App.run_after_keychord ( ' backspace ' )
check_eq ( Lines [ 1 ] . data , ' abc ' , " F - test_backspace_to_start_of_line/data:1 " )
check_eq ( Lines [ 2 ] . data , ' f ' , " F - test_backspace_to_start_of_line/data:2 " )
-- cursor remains at start of selection
check_eq ( Cursor1.line , 2 , " F - test_backspace_to_start_of_line/cursor:line " )
check_eq ( Cursor1.pos , 1 , " F - test_backspace_to_start_of_line/cursor:pos " )
-- selection is cleared
check_nil ( Selection1.line , " F - test_backspace_to_start_of_line/selection " )
end
2022-06-02 23:45:25 +01:00
function test_undo_insert_text ( )
io.write ( ' \n test_undo_insert_text ' )
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' def ' , ' xyz ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 4 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
-- insert a character
App.run_after_textinput ( ' g ' )
check_eq ( Cursor1.line , 2 , ' F - test_undo_insert_text/baseline/cursor:line ' )
check_eq ( Cursor1.pos , 5 , ' F - test_undo_insert_text/baseline/cursor:pos ' )
check_nil ( Selection1.line , ' F - test_undo_insert_text/baseline/selection:line ' )
check_nil ( Selection1.pos , ' F - test_undo_insert_text/baseline/selection:pos ' )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_undo_insert_text/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' defg ' , ' F - test_undo_insert_text/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' xyz ' , ' F - test_undo_insert_text/baseline/screen:3 ' )
-- undo
App.run_after_keychord ( ' M-z ' )
check_eq ( Cursor1.line , 2 , ' F - test_undo_insert_text/cursor:line ' )
check_eq ( Cursor1.pos , 4 , ' F - test_undo_insert_text/cursor:pos ' )
check_nil ( Selection1.line , ' F - test_undo_insert_text/selection:line ' )
check_nil ( Selection1.pos , ' F - test_undo_insert_text/selection:pos ' )
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_undo_insert_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_undo_insert_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' xyz ' , ' F - test_undo_insert_text/screen:3 ' )
end
function test_undo_delete_text ( )
io.write ( ' \n test_undo_delete_text ' )
App.screen . init { width = 120 , height = 60 }
Lines = load_array { ' abc ' , ' defg ' , ' xyz ' }
Line_width = App.screen . width
Cursor1 = { line = 2 , pos = 5 }
Screen_top1 = { line = 1 , pos = 1 }
Screen_bottom1 = { }
Zoom = 1
-- delete a character
App.run_after_keychord ( ' backspace ' )
check_eq ( Cursor1.line , 2 , ' F - test_undo_delete_text/baseline/cursor:line ' )
check_eq ( Cursor1.pos , 4 , ' F - test_undo_delete_text/baseline/cursor:pos ' )
check_nil ( Selection1.line , ' F - test_undo_delete_text/baseline/selection:line ' )
check_nil ( Selection1.pos , ' F - test_undo_delete_text/baseline/selection:pos ' )
local screen_top_margin = 15 -- pixels
local line_height = 15 -- pixels
local y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_undo_delete_text/baseline/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' def ' , ' F - test_undo_delete_text/baseline/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' xyz ' , ' F - test_undo_delete_text/baseline/screen:3 ' )
-- undo
--? -- after undo, the backspaced key is selected
App.run_after_keychord ( ' M-z ' )
check_eq ( Cursor1.line , 2 , ' F - test_undo_delete_text/cursor:line ' )
check_eq ( Cursor1.pos , 5 , ' F - test_undo_delete_text/cursor:pos ' )
check_nil ( Selection1.line , ' F - test_undo_delete_text/selection:line ' )
check_nil ( Selection1.pos , ' F - test_undo_delete_text/selection:pos ' )
--? check_eq(Selection1.line, 2, 'F - test_undo_delete_text/selection:line')
--? check_eq(Selection1.pos, 4, 'F - test_undo_delete_text/selection:pos')
y = screen_top_margin
App.screen . check ( y , ' abc ' , ' F - test_undo_delete_text/screen:1 ' )
y = y + line_height
App.screen . check ( y , ' defg ' , ' F - test_undo_delete_text/screen:2 ' )
y = y + line_height
App.screen . check ( y , ' xyz ' , ' F - test_undo_delete_text/screen:3 ' )
end
2022-06-03 00:29:18 +01:00
function test_zzz_undo_load_test ( )
print ( ' \n \n Testing a 10KB file ' )
-- create a large list of lines
local lines = { }
-- 10k characters, hundred lines, 2k words
for i = 1 , 100 do
local line = ' '
for c = 1 , 20 do
line = line .. ' abcd '
end
end
Lines = load_array ( lines )
-- perform 1000 mutations
print ( ' are the dots printing quickly and without any pauses? ' )
for i = 1 , 1000 do
if i % 50 == 0 then
App.run_after_keychord ( ' return ' )
else
App.run_after_textinput ( ' a ' )
end
if i % 10 == 0 then
io.write ( i )
io.write ( ' ' )
io.flush ( )
end
end
end
2022-05-20 06:58:04 +01:00
function Text . compute_fragments ( line , line_width )
2022-05-25 21:35:32 +01:00
--? print('compute_fragments', line_width)
2022-05-20 06:58:04 +01:00
line.fragments = { }
local x = 25
-- try to wrap at word boundaries
for frag in line.data : gmatch ( ' %S*%s* ' ) do
2022-05-23 07:17:06 +01:00
local frag_text = App.newText ( love.graphics . getFont ( ) , frag )
local frag_width = math.floor ( App.width ( frag_text ) * Zoom )
2022-05-20 06:58:04 +01:00
--? print('x: '..tostring(x)..'; '..tostring(line_width-x)..'px to go')
--? print('frag: ^'..frag..'$ is '..tostring(frag_width)..'px wide')
if x + frag_width > line_width then
while x + frag_width > line_width do
2022-05-28 17:02:15 +01:00
--? print(x, frag, frag_width, line_width)
2022-05-20 06:58:04 +01:00
if x < 0.8 * line_width then
2022-05-23 23:38:42 +01:00
--? print(frag, x, frag_width, line_width)
2022-05-20 06:58:04 +01:00
-- long word; chop it at some letter
-- We're not going to reimplement TeX here.
2022-05-23 23:22:16 +01:00
local b = Text.nearest_pos_less_than ( frag , line_width - x )
2022-05-28 17:02:15 +01:00
assert ( b > 0 ) -- avoid infinite loop when window is too narrow
2022-05-20 06:58:04 +01:00
--? print('space for '..tostring(b)..' graphemes')
local frag1 = string.sub ( frag , 1 , b )
2022-05-23 07:17:06 +01:00
local frag1_text = App.newText ( love.graphics . getFont ( ) , frag1 )
2022-05-23 23:22:16 +01:00
local frag1_width = math.floor ( App.width ( frag1_text ) * Zoom )
2022-05-23 23:38:42 +01:00
--? print(frag, x, frag1_width, line_width)
2022-05-23 23:22:16 +01:00
assert ( x + frag1_width <= line_width )
2022-05-20 06:58:04 +01:00
--? print('inserting '..frag1..' of width '..tostring(frag1_width)..'px')
table.insert ( line.fragments , { data = frag1 , text = frag1_text } )
frag = string.sub ( frag , b + 1 )
2022-05-23 07:17:06 +01:00
frag_text = App.newText ( love.graphics . getFont ( ) , frag )
frag_width = math.floor ( App.width ( frag_text ) * Zoom )
2022-05-20 06:58:04 +01:00
end
x = 25 -- new line
end
end
if # frag > 0 then
--? print('inserting '..frag..' of width '..tostring(frag_width)..'px')
table.insert ( line.fragments , { data = frag , text = frag_text } )
end
2022-05-28 17:02:15 +01:00
x = x + frag_width
2022-05-20 06:58:04 +01:00
end
end
2022-05-21 22:03:06 +01:00
function Text . textinput ( t )
2022-05-18 06:36:10 +01:00
if love.mouse . isDown ( ' 1 ' ) then return end
2022-05-30 23:36:53 +01:00
if App.ctrl_down ( ) or App.alt_down ( ) or App.cmd_down ( ) then return end
2022-05-27 04:40:50 +01:00
local down = love.keyboard . isDown
2022-05-19 04:22:57 +01:00
Text.insert_at_cursor ( t )
end
function Text . insert_at_cursor ( t )
2022-05-29 15:57:35 +01:00
if Selection1.line then Text.delete_selection ( ) end
2022-06-02 23:45:25 +01:00
-- Collect what you did in an event that can be undone.
2022-06-03 00:44:18 +01:00
local before = snapshot ( )
2022-05-18 06:36:10 +01:00
local byte_offset
2022-05-22 00:24:07 +01:00
if Cursor1.pos > 1 then
2022-05-29 15:57:35 +01:00
byte_offset = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos )
2022-05-18 06:36:10 +01:00
else
2022-05-29 15:57:35 +01:00
byte_offset = 1
2022-05-18 06:36:10 +01:00
end
2022-05-29 15:57:35 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_offset - 1 ) .. t .. string.sub ( Lines [ Cursor1.line ] . data , byte_offset )
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . fragments = nil
2022-05-25 22:31:16 +01:00
Lines [ Cursor1.line ] . screen_line_starting_pos = nil
2022-05-22 00:24:07 +01:00
Cursor1.pos = Cursor1.pos + 1
2022-06-02 23:45:25 +01:00
-- finalize undo event
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-05-18 06:36:10 +01:00
end
-- Don't handle any keys here that would trigger love.textinput above.
function Text . keychord_pressed ( chord )
2022-05-29 16:12:47 +01:00
--? print(chord)
2022-05-29 06:27:47 +01:00
--== shortcuts that mutate text
2022-05-18 06:36:10 +01:00
if chord == ' return ' then
2022-06-03 00:44:18 +01:00
local before = snapshot ( )
2022-05-22 00:24:07 +01:00
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 ) } )
2022-05-31 01:24:23 +01:00
local scroll_down = ( Cursor_y + math.floor ( 15 * Zoom ) ) > App.screen . height
2022-05-22 00:24:07 +01:00
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
2022-05-18 06:36:10 +01:00
save_to_disk ( Lines , Filename )
2022-05-31 01:24:23 +01:00
if scroll_down then
2022-05-28 16:38:49 +01:00
Screen_top1.line = Cursor1.line
Text.scroll_up_while_cursor_on_screen ( )
end
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-05-19 04:22:57 +01:00
elseif chord == ' tab ' then
2022-06-03 00:44:18 +01:00
local before = snapshot ( )
2022-05-19 04:22:57 +01:00
Text.insert_at_cursor ( ' \t ' )
save_to_disk ( Lines , Filename )
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-05-18 06:36:10 +01:00
elseif chord == ' backspace ' then
2022-06-03 00:44:18 +01:00
local before = snapshot ( )
2022-05-29 16:12:47 +01:00
if Selection1.line then
Text.delete_selection ( )
2022-06-02 23:45:25 +01:00
save_to_disk ( Lines , Filename )
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-05-29 16:12:47 +01:00
return
end
2022-05-22 00:24:07 +01:00
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 )
2022-05-18 06:36:10 +01:00
if byte_start then
if byte_end then
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 ) .. string.sub ( Lines [ Cursor1.line ] . data , byte_end )
2022-05-18 06:36:10 +01:00
else
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 )
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . fragments = nil
Cursor1.pos = Cursor1.pos - 1
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
elseif Cursor1.line > 1 then
if Lines [ Cursor1.line - 1 ] . mode == ' drawing ' then
table.remove ( Lines , Cursor1.line - 1 )
2022-05-18 06:36:10 +01:00
else
-- join lines
2022-05-22 00:24:07 +01:00
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 )
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
Cursor1.line = Cursor1.line - 1
2022-05-18 06:36:10 +01:00
end
2022-05-29 07:11:53 +01:00
if Text.lt1 ( Cursor1 , Screen_top1 ) then
2022-05-28 17:41:02 +01:00
local top2 = Text.to2 ( Screen_top1 )
top2 = Text.previous_screen_line ( top2 )
Screen_top1 = Text.to1 ( top2 )
end
2022-05-29 07:11:53 +01:00
assert ( Text.le1 ( Screen_top1 , Cursor1 ) )
2022-05-18 06:36:10 +01:00
save_to_disk ( Lines , Filename )
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-05-18 06:36:10 +01:00
elseif chord == ' delete ' then
2022-06-03 00:44:18 +01:00
local before = snapshot ( )
2022-05-29 16:12:47 +01:00
if Selection1.line then
Text.delete_selection ( )
2022-06-02 23:45:25 +01:00
save_to_disk ( Lines , Filename )
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-05-29 16:12:47 +01:00
return
end
2022-05-22 00:24:07 +01:00
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 )
2022-05-18 06:36:10 +01:00
if byte_start then
if byte_end then
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 ) .. string.sub ( Lines [ Cursor1.line ] . data , byte_end )
2022-05-18 06:36:10 +01:00
else
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = string.sub ( Lines [ Cursor1.line ] . data , 1 , byte_start - 1 )
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . fragments = nil
-- no change to Cursor1.pos
2022-05-18 06:36:10 +01:00
end
2022-05-22 00:24:07 +01:00
elseif Cursor1.line < # Lines then
if Lines [ Cursor1.line + 1 ] . mode == ' drawing ' then
table.remove ( Lines , Cursor1.line + 1 )
2022-05-18 06:36:10 +01:00
else
-- join lines
2022-05-22 00:24:07 +01:00
Lines [ Cursor1.line ] . data = Lines [ Cursor1.line ] . data .. Lines [ Cursor1.line + 1 ] . data
Lines [ Cursor1.line ] . fragments = nil
table.remove ( Lines , Cursor1.line + 1 )
2022-05-18 06:36:10 +01:00
end
end
save_to_disk ( Lines , Filename )
2022-06-03 00:44:18 +01:00
record_undo_event ( { before = before , after = snapshot ( ) } )
2022-06-02 23:45:25 +01:00
-- undo/redo really belongs in main.lua, but it's here so I can test the
-- text-specific portions of it
elseif chord == ' M-z ' then
local event = undo_event ( )
if event then
local src = event.before
Screen_top1 = deepcopy ( src.screen_top )
Cursor1 = deepcopy ( src.cursor )
Selection1 = deepcopy ( src.selection )
if src.lines then
Lines = deepcopy ( src.lines )
end
end
elseif chord == ' M-y ' then
local event = redo_event ( )
if event then
local src = event.after
Screen_top1 = deepcopy ( src.screen_top )
Cursor1 = deepcopy ( src.cursor )
Selection1 = deepcopy ( src.selection )
if src.lines then
Lines = deepcopy ( src.lines )
--? for _,line in ipairs(Lines) do
--? if line.mode == 'drawing' then
--? print('restoring', line.points, 'with', #line.points, 'points')
--? print('restoring', line.shapes, 'with', #line.shapes, 'shapes')
--? end
--? end
end
end
2022-05-29 06:27:47 +01:00
-- paste
2022-05-29 23:33:24 +01:00
elseif chord == ' M-c ' then
local s = Text.selection ( )
if s then
love.system . setClipboardText ( s )
end
elseif chord == ' M-x ' then
local s = Text.cut_selection ( )
if s then
love.system . setClipboardText ( s )
end
2022-05-29 06:27:47 +01:00
elseif chord == ' M-v ' then
local s = love.system . getClipboardText ( )
for _ , code in utf8.codes ( s ) do
Text.insert_at_cursor ( utf8.char ( code ) )
end
--== shortcuts that move the cursor
elseif chord == ' left ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
2022-05-29 06:27:47 +01:00
Text.left ( )
elseif chord == ' right ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
Text.right ( )
elseif chord == ' S-left ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
Text.left ( )
elseif chord == ' S-right ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
2022-05-29 06:27:47 +01:00
Text.right ( )
-- C- hotkeys reserved for drawings, so we'll use M-
elseif chord == ' M-left ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
2022-05-29 06:36:05 +01:00
Text.word_left ( )
2022-05-29 06:27:47 +01:00
elseif chord == ' M-right ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
Text.word_right ( )
elseif chord == ' M-S-left ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
Text.word_left ( )
elseif chord == ' M-S-right ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
2022-05-29 06:36:05 +01:00
Text.word_right ( )
2022-05-29 06:27:47 +01:00
elseif chord == ' home ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
2022-05-29 06:27:47 +01:00
Cursor1.pos = 1
elseif chord == ' end ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
Cursor1.pos = utf8.len ( Lines [ Cursor1.line ] . data ) + 1
elseif chord == ' S-home ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
Cursor1.pos = 1
elseif chord == ' S-end ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
2022-05-29 06:27:47 +01:00
Cursor1.pos = utf8.len ( Lines [ Cursor1.line ] . data ) + 1
2022-05-18 06:36:10 +01:00
elseif chord == ' up ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
2022-05-29 06:36:05 +01:00
Text.up ( )
2022-05-18 06:36:10 +01:00
elseif chord == ' down ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
Text.down ( )
elseif chord == ' S-up ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
Text.up ( )
elseif chord == ' S-down ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
2022-05-29 06:36:05 +01:00
Text.down ( )
2022-05-29 06:42:11 +01:00
elseif chord == ' pageup ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
2022-05-29 06:45:53 +01:00
Text.pageup ( )
2022-05-29 06:42:11 +01:00
elseif chord == ' pagedown ' then
2022-05-29 16:12:47 +01:00
if Selection1.line then
Selection1 = { }
end
Text.pagedown ( )
elseif chord == ' S-pageup ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
Text.pageup ( )
elseif chord == ' S-pagedown ' then
if Selection1.line == nil then
Selection1 = { line = Cursor1.line , pos = Cursor1.pos }
end
2022-05-29 06:45:53 +01:00
Text.pagedown ( )
2022-05-29 06:36:05 +01:00
end
end
2022-05-29 06:45:53 +01:00
function Text . pageup ( )
-- duplicate some logic from love.draw
local top2 = Text.to2 ( Screen_top1 )
--? print(App.screen.height)
local y = App.screen . height - math.floor ( 15 * Zoom )
while y >= 15 do
--? print(y, top2.line)
if Screen_top1.line == 1 and Screen_top1.pos == 1 then break end
if Lines [ Screen_top1.line ] . mode == ' text ' then
y = y - math.floor ( 15 * Zoom )
elseif Lines [ Screen_top1.line ] . mode == ' drawing ' then
y = y - 20 - Drawing.pixels ( Lines [ Screen_top1.line ] . h )
end
top2 = Text.previous_screen_line ( top2 )
end
Screen_top1 = Text.to1 ( top2 )
Cursor1.line = Screen_top1.line
Cursor1.pos = Screen_top1.pos
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary ( )
--? print(Cursor1.line, Cursor1.pos, Screen_top1.line, Screen_top1.pos)
end
function Text . pagedown ( )
Screen_top1.line = Screen_bottom1.line
Screen_top1.pos = Screen_bottom1.pos
--? print('setting top to', Screen_top1.line, Screen_top1.pos)
Cursor1.line = Screen_top1.line
Cursor1.pos = Screen_top1.pos
Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary ( )
--? print('top now', Screen_top1.line)
end
2022-05-29 06:36:05 +01:00
function Text . up ( )
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
--? print('up', Cursor1.pos, Screen_top1.pos)
local screen_line_index , screen_line_starting_pos = Text.pos_at_start_of_cursor_screen_line ( )
if screen_line_starting_pos == 1 then
--? print('cursor is at first screen line of its line')
-- line is done; skip to previous text line
local new_cursor_line = Cursor1.line
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line - 1
if Lines [ new_cursor_line ] . mode == ' text ' then
--? print('found previous text line')
Cursor1.line = new_cursor_line
Text.populate_screen_line_starting_pos ( Cursor1.line )
if Lines [ Cursor1.line ] . screen_line_starting_pos == nil then
2022-05-22 00:24:07 +01:00
Cursor1.pos = Text.nearest_cursor_pos ( Lines [ Cursor1.line ] . data , Cursor_x )
2022-05-21 04:33:30 +01:00
break
end
2022-05-29 06:36:05 +01:00
-- previous text line found, pick its final screen line
--? print('has multiple screen lines')
local screen_line_starting_pos = Lines [ Cursor1.line ] . screen_line_starting_pos
--? print(#screen_line_starting_pos)
screen_line_starting_pos = screen_line_starting_pos [ # screen_line_starting_pos ]
--? print('previous screen line starts at pos '..tostring(screen_line_starting_pos)..' of its line')
if Screen_top1.line > Cursor1.line then
Screen_top1.line = Cursor1.line
Screen_top1.pos = screen_line_starting_pos
--? print('pos of top of screen is also '..tostring(Screen_top1.pos)..' of the same line')
end
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
break
2022-05-18 06:36:10 +01:00
end
2022-05-29 06:36:05 +01:00
end
if Cursor1.line < Screen_top1.line then
Screen_top1.line = Cursor1.line
end
else
-- move up one screen line in current line
--? print('cursor is NOT at first screen line of its line')
assert ( screen_line_index > 1 )
new_screen_line_starting_pos = Lines [ Cursor1.line ] . screen_line_starting_pos [ screen_line_index - 1 ]
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_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))
end
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))
end
end
function Text . down ( )
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
--? print('down', Cursor1.line, Cursor1.pos, Screen_top1.line, Screen_top1.pos, Screen_bottom1.line, Screen_bottom1.pos)
if Text.cursor_at_final_screen_line ( ) then
-- line is done, skip to next text line
--? print('cursor at final screen line of its line')
local new_cursor_line = Cursor1.line
while new_cursor_line < # Lines do
new_cursor_line = new_cursor_line + 1
if Lines [ new_cursor_line ] . mode == ' text ' then
Cursor1.line = new_cursor_line
Cursor1.pos = Text.nearest_cursor_pos ( Lines [ Cursor1.line ] . data , Cursor_x )
--? print(Cursor1.pos)
break
2022-05-24 23:01:09 +01:00
end
2022-05-19 01:22:04 +01:00
end
2022-05-29 06:36:05 +01:00
if Cursor1.line > Screen_bottom1.line then
--? print('screen top before:', Screen_top1.line, Screen_top1.pos)
Screen_top1.line = Cursor1.line
--? print('scroll up preserving cursor')
Text.scroll_up_while_cursor_on_screen ( )
--? print('screen top after:', Screen_top1.line, Screen_top1.pos)
end
else
-- move down one screen line in current line
local scroll_up = false
2022-05-29 07:11:53 +01:00
if Text.le1 ( Screen_bottom1 , Cursor1 ) then
2022-05-29 06:36:05 +01:00
scroll_up = true
end
--? print('cursor is NOT at final screen line of its line')
local screen_line_index , screen_line_starting_pos = Text.pos_at_start_of_cursor_screen_line ( )
new_screen_line_starting_pos = Lines [ Cursor1.line ] . screen_line_starting_pos [ screen_line_index + 1 ]
--? print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_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', Cursor1.line, Cursor1.pos)
if scroll_up then
Screen_top1.line = Cursor1.line
--? print('scroll up preserving cursor')
Text.scroll_up_while_cursor_on_screen ( )
--? print('screen top after:', Screen_top1.line, Screen_top1.pos)
end
end
--? print('=>', Cursor1.line, Cursor1.pos, Screen_top1.line, Screen_top1.pos, Screen_bottom1.line, Screen_bottom1.pos)
end
function Text . word_left ( )
while true do
Text.left ( )
if Cursor1.pos == 1 then break end
assert ( Cursor1.pos > 1 )
local offset = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos )
assert ( offset > 1 )
if Lines [ Cursor1.line ] . data : sub ( offset - 1 , offset - 1 ) == ' ' then
break
end
end
end
function Text . word_right ( )
while true do
Text.right ( )
if Cursor1.pos > utf8.len ( Lines [ Cursor1.line ] . data ) then break end
local offset = utf8.offset ( Lines [ Cursor1.line ] . data , Cursor1.pos )
2022-05-29 16:12:47 +01:00
if Lines [ Cursor1.line ] . data : sub ( offset , offset ) == ' ' then -- TODO: other space characters
2022-05-29 06:36:05 +01:00
break
end
2022-05-18 06:36:10 +01:00
end
end
2022-05-25 23:25:10 +01:00
function Text . left ( )
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
if Cursor1.pos > 1 then
Cursor1.pos = Cursor1.pos - 1
else
local new_cursor_line = Cursor1.line
while new_cursor_line > 1 do
new_cursor_line = new_cursor_line - 1
if Lines [ new_cursor_line ] . mode == ' text ' then
Cursor1.line = new_cursor_line
Cursor1.pos = utf8.len ( Lines [ Cursor1.line ] . data ) + 1
break
end
end
if Cursor1.line < Screen_top1.line then
Screen_top1.line = Cursor1.line
end
end
end
function Text . right ( )
assert ( Lines [ Cursor1.line ] . mode == ' text ' )
if Cursor1.pos <= utf8.len ( Lines [ Cursor1.line ] . data ) then
Cursor1.pos = Cursor1.pos + 1
else
local new_cursor_line = Cursor1.line
while new_cursor_line <= # Lines - 1 do
new_cursor_line = new_cursor_line + 1
if Lines [ new_cursor_line ] . mode == ' text ' then
Cursor1.line = new_cursor_line
Cursor1.pos = 1
break
end
end
if Cursor1.line > Screen_bottom1.line then
Screen_top1.line = Cursor1.line
end
end
end
2022-05-21 00:41:21 +01:00
function Text . pos_at_start_of_cursor_screen_line ( )
2022-05-22 00:24:07 +01:00
if Lines [ Cursor1.line ] . screen_line_starting_pos == nil then
2022-05-21 00:41:21 +01:00
return 1 , 1
end
2022-05-22 00:24:07 +01:00
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
2022-05-21 00:41:21 +01:00
return i , spos
end
end
assert ( false )
end
2022-05-21 04:33:30 +01:00
function Text . cursor_at_final_screen_line ( )
2022-05-22 00:24:07 +01:00
if Lines [ Cursor1.line ] . screen_line_starting_pos == nil then
2022-05-21 04:33:30 +01:00
return true
end
2022-05-25 22:56:21 +01:00
local screen_lines = Lines [ Cursor1.line ] . screen_line_starting_pos
2022-05-23 23:38:42 +01:00
--? print(screen_lines[#screen_lines], Cursor1.pos)
2022-05-22 05:08:21 +01:00
return screen_lines [ # screen_lines ] <= Cursor1.pos
2022-05-21 04:33:30 +01:00
end
2022-05-19 02:19:27 +01:00
function Text . move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary ( )
2022-05-23 16:52:13 +01:00
local y = 15 -- top margin
2022-05-22 00:24:07 +01:00
while Cursor1.line <= # Lines do
if Lines [ Cursor1.line ] . mode == ' text ' then
2022-05-19 02:19:27 +01:00
break
end
2022-05-23 23:38:42 +01:00
--? print('cursor skips', Cursor1.line)
2022-05-23 16:52:13 +01:00
y = y + 20 + Drawing.pixels ( Lines [ Cursor1.line ] . h )
2022-05-22 00:24:07 +01:00
Cursor1.line = Cursor1.line + 1
2022-05-19 02:19:27 +01:00
end
-- hack: insert a text line at bottom of file if necessary
2022-05-22 00:24:07 +01:00
if Cursor1.line > # Lines then
assert ( Cursor1.line == # Lines + 1 )
2022-05-19 02:19:27 +01:00
table.insert ( Lines , { mode = ' text ' , data = ' ' } )
end
2022-05-23 16:52:13 +01:00
--? print(y, App.screen.height, App.screen.height-math.floor(15*Zoom))
if y > App.screen . height - math.floor ( 15 * Zoom ) then
--? if Cursor1.line > Screen_bottom1.line then
2022-05-23 23:38:42 +01:00
--? print('scroll up')
2022-05-22 00:24:07 +01:00
Screen_top1.line = Cursor1.line
2022-05-19 02:19:27 +01:00
Text.scroll_up_while_cursor_on_screen ( )
end
end
function Text . scroll_up_while_cursor_on_screen ( )
2022-05-22 07:02:04 +01:00
local top2 = Text.to2 ( Cursor1 )
--? print('cursor pos '..tostring(Cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down')
2022-05-23 16:13:58 +01:00
local y = App.screen . height - math.floor ( 15 * Zoom )
2022-05-21 06:43:09 +01:00
-- duplicate some logic from love.draw
2022-05-19 02:19:27 +01:00
while true do
2022-05-22 07:02:04 +01:00
--? print(y, 'top2:', top2.line, top2.screen_line, top2.screen_pos)
if top2.line == 1 and top2.screen_line == 1 then break end
if top2.screen_line > 1 or Lines [ top2.line - 1 ] . mode == ' text ' then
local h = math.floor ( 15 * Zoom )
if y - h < 15 then -- top margin = 15
break
end
y = y - h
2022-05-21 06:43:09 +01:00
else
2022-05-22 07:02:04 +01:00
assert ( top2.line > 1 )
assert ( Lines [ top2.line - 1 ] . mode == ' drawing ' )
-- We currently can't draw partial drawings, so either skip it entirely
-- or not at all.
2022-05-22 17:15:56 +01:00
local h = 20 + Drawing.pixels ( Lines [ top2.line - 1 ] . h )
2022-05-22 07:02:04 +01:00
if y - h < 15 then
break
end
--? print('skipping drawing of height', h)
y = y - h
2022-05-19 02:19:27 +01:00
end
2022-05-22 07:02:04 +01:00
top2 = Text.previous_screen_line ( top2 )
2022-05-19 02:19:27 +01:00
end
2022-05-22 07:02:04 +01:00
--? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos)
Screen_top1 = Text.to1 ( top2 )
--? print('top1 finally:', Screen_top1.line, Screen_top1.pos)
2022-05-19 02:19:27 +01:00
end
2022-05-18 06:53:09 +01:00
function Text . in_line ( line , x , y )
2022-05-19 01:22:04 +01:00
if line.y == nil then return false end -- outside current page
2022-05-25 21:38:33 +01:00
if x < 25 then return false end
2022-05-20 06:07:12 +01:00
if y < line.y then return false end
if line.screen_line_starting_pos == nil then return y < line.y + math.floor ( 15 * Zoom ) end
return y < line.y + # line.screen_line_starting_pos * math.floor ( 15 * Zoom )
2022-05-18 06:53:09 +01:00
end
2022-05-25 23:20:18 +01:00
-- mx,my in pixels
2022-05-20 06:07:12 +01:00
function Text . move_cursor ( line_index , line , mx , my )
2022-05-22 00:24:07 +01:00
Cursor1.line = line_index
2022-05-20 06:07:12 +01:00
if line.screen_line_starting_pos == nil then
2022-05-22 00:24:07 +01:00
Cursor1.pos = Text.nearest_cursor_pos ( line.data , mx )
2022-05-20 06:07:12 +01:00
return
end
assert ( line.fragments )
assert ( my >= line.y )
-- duplicate some logic from Text.draw
local y = line.y
2022-05-20 06:26:15 +01:00
for screen_line_index , screen_line_starting_pos in ipairs ( line.screen_line_starting_pos ) do
2022-05-20 06:07:12 +01:00
local nexty = y + math.floor ( 15 * Zoom )
if my < nexty then
2022-05-20 06:26:15 +01:00
-- On all wrapped screen lines but the final one, clicks past end of
-- line position cursor on final character of screen line.
-- (The final screen line positions past end of screen line as always.)
if mx > Line_width and screen_line_index < # line.screen_line_starting_pos then
2022-05-22 00:24:07 +01:00
Cursor1.pos = line.screen_line_starting_pos [ screen_line_index + 1 ]
2022-05-20 06:26:15 +01:00
return
end
2022-05-20 06:07:12 +01:00
local s = string.sub ( line.data , screen_line_starting_pos )
2022-05-22 00:24:07 +01:00
Cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos ( s , mx ) - 1
2022-05-20 06:07:12 +01:00
return
end
y = nexty
end
assert ( false )
2022-05-18 06:53:09 +01:00
end
2022-05-20 06:07:12 +01:00
-- manual test:
-- line: abc
-- def
-- gh
-- fragments: abc, def, gh
-- click inside e
-- line_starting_pos = 1 + 3 = 4
-- nearest_cursor_pos('defgh', mx) = 2
2022-05-22 00:24:07 +01:00
-- Cursor1.pos = 4 + 2 - 1 = 5
2022-05-20 06:07:12 +01:00
-- manual test:
-- click inside h
-- line_starting_pos = 1 + 3 + 3 = 7
-- nearest_cursor_pos('gh', mx) = 2
2022-05-22 00:24:07 +01:00
-- Cursor1.pos = 7 + 2 - 1 = 8
2022-05-18 06:53:09 +01:00
2022-05-23 23:22:16 +01:00
function Text . nearest_cursor_pos ( line , x ) -- x includes left margin
2022-05-18 04:29:08 +01:00
if x == 0 then
return 1
end
2022-05-20 01:46:02 +01:00
local len = utf8.len ( line )
2022-05-25 23:20:18 +01:00
local max_x = 25 + Text.x ( line , len + 1 )
2022-05-18 04:29:08 +01:00
if x > max_x then
2022-05-20 01:46:02 +01:00
return len + 1
2022-05-18 04:29:08 +01:00
end
2022-05-20 01:46:02 +01:00
local left , right = 1 , len + 1
2022-05-24 22:34:50 +01:00
--? print('-- nearest', x)
2022-05-20 05:52:38 +01:00
while true do
2022-05-24 22:34:50 +01:00
--? print('nearest', x, '^'..line..'$', left, right)
if left == right then
return left
end
2022-05-18 04:29:08 +01:00
local curr = math.floor ( ( left + right ) / 2 )
2022-05-25 23:20:18 +01:00
local currxmin = 25 + Text.x ( line , curr )
local currxmax = 25 + Text.x ( line , curr + 1 )
2022-05-24 22:34:50 +01:00
--? print('nearest', x, left, right, curr, currxmin, currxmax)
2022-05-18 04:29:08 +01:00
if currxmin <= x and x < currxmax then
2022-05-25 23:08:37 +01:00
if x - currxmin < currxmax - x then
return curr
else
return curr + 1
end
2022-05-18 04:29:08 +01:00
end
2022-05-20 05:52:38 +01:00
if left >= right - 1 then
return right
end
2022-05-18 04:29:08 +01:00
if currxmin > x then
right = curr
else
left = curr
end
end
2022-05-20 05:52:38 +01:00
assert ( false )
2022-05-18 04:29:08 +01:00
end
2022-05-23 23:22:16 +01:00
function Text . nearest_pos_less_than ( line , x ) -- x DOES NOT include left margin
if x == 0 then
return 1
end
local len = utf8.len ( line )
2022-05-25 23:20:18 +01:00
local max_x = Text.x ( line , len + 1 )
2022-05-23 23:22:16 +01:00
if x > max_x then
return len + 1
end
local left , right = 1 , len + 1
--? print('--')
while true do
local curr = math.floor ( ( left + right ) / 2 )
2022-05-25 23:20:18 +01:00
local currxmin = Text.x ( line , curr + 1 )
local currxmax = Text.x ( line , curr + 2 )
2022-05-23 23:22:16 +01:00
--? print(x, left, right, curr, currxmin, currxmax)
if currxmin <= x and x < currxmax then
return curr
end
if left >= right - 1 then
return left
end
if currxmin > x then
right = curr
else
left = curr
end
end
assert ( false )
end
2022-05-25 23:20:18 +01:00
function Text . x ( s , pos )
local offset = utf8.offset ( s , pos )
assert ( offset )
local s_before = s : sub ( 1 , offset - 1 )
local text_before = App.newText ( love.graphics . getFont ( ) , s_before )
return math.floor ( App.width ( text_before ) * Zoom )
2022-05-20 01:29:14 +01:00
end
2022-05-22 00:24:07 +01:00
function Text . to2 ( pos1 )
2022-05-30 23:40:36 +01:00
if Lines [ pos1.line ] . mode == ' drawing ' then
return { line = pos1.line , screen_line = 1 , screen_pos = 1 }
end
2022-05-22 00:24:07 +01:00
local result = { line = pos1.line , screen_line = 1 }
2022-05-22 06:08:13 +01:00
if Lines [ pos1.line ] . screen_line_starting_pos == nil then
2022-05-22 00:24:07 +01:00
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 ]
2022-05-22 07:02:04 +01:00
if spos <= pos1.pos then
2022-05-22 00:24:07 +01:00
result.screen_line = i
2022-05-22 07:02:04 +01:00
result.screen_pos = pos1.pos - spos + 1
2022-05-22 00:24:07 +01:00
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
2022-05-22 07:02:04 +01:00
result.pos = Lines [ pos2.line ] . screen_line_starting_pos [ pos2.screen_line ] + pos2.screen_pos - 1
2022-05-22 00:24:07 +01:00
end
return result
end
2022-05-29 07:11:53 +01:00
function Text . lt1 ( a , b )
if a.line < b.line then
return true
end
if a.line > b.line then
return false
end
return a.pos < b.pos
end
function Text . le1 ( a , b )
if a.line < b.line then
return true
end
if a.line > b.line then
return false
end
return a.pos <= b.pos
end
2022-05-22 07:02:04 +01:00
function Text . previous_screen_line ( pos2 )
if pos2.screen_line > 1 then
return { line = pos2.line , screen_line = pos2.screen_line - 1 , screen_pos = 1 }
elseif pos2.line == 1 then
return pos2
elseif Lines [ pos2.line - 1 ] . mode == ' drawing ' then
return { line = pos2.line - 1 , screen_line = 1 , screen_pos = 1 }
else
local l = Lines [ pos2.line - 1 ]
if l.screen_line_starting_pos == nil then
return { line = pos2.line - 1 , screen_line = 1 , screen_pos = 1 }
else
return { line = pos2.line - 1 , screen_line =# Lines [ pos2.line - 1 ] . screen_line_starting_pos , screen_pos = 1 }
end
end
end
2022-05-24 01:39:00 +01:00
function Text . populate_screen_line_starting_pos ( line_index )
2022-05-25 21:35:32 +01:00
--? print('Text.populate_screen_line_starting_pos')
2022-05-24 01:39:00 +01:00
local line = Lines [ line_index ]
2022-05-25 22:56:21 +01:00
if line.screen_line_starting_pos then
return
end
-- duplicate some logic from Text.draw
2022-05-24 01:39:00 +01:00
if line.fragments == nil then
Text.compute_fragments ( line , Line_width )
end
local x = 25
local pos = 1
for _ , f in ipairs ( line.fragments ) do
local frag , frag_text = f.data , f.text
--? print(x, frag)
-- render fragment
local frag_width = math.floor ( App.width ( frag_text ) * Zoom )
if x + frag_width > Line_width then
x = 25
if line.screen_line_starting_pos == nil then
line.screen_line_starting_pos = { 1 , pos }
else
2022-05-25 22:56:21 +01:00
--? print(' ', #line.screen_line_starting_pos, line.data)
2022-05-24 01:39:00 +01:00
table.insert ( line.screen_line_starting_pos , pos )
end
end
x = x + frag_width
local frag_len = utf8.len ( frag )
pos = pos + frag_len
end
end
2022-05-18 04:29:08 +01:00
return Text