diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2024-06-11 12:13:18 -0700 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2024-06-11 12:13:18 -0700 |
commit | 262c078133e1603aa30b3404f4f4b12428d925e5 (patch) | |
tree | 8101bf50db1523f2fc1c4f8d8f6ca7500355ff10 /text.lua | |
parent | a244738b01a2f471fc5bb9ac04421aaa17c6e66c (diff) | |
parent | 55f5c2d696ffba6c5d220d8e317c15ed2335ac7b (diff) | |
download | view.love-262c078133e1603aa30b3404f4f4b12428d925e5.tar.gz |
Merge lines.love
Diffstat (limited to 'text.lua')
-rw-r--r-- | text.lua | 175 |
1 files changed, 106 insertions, 69 deletions
diff --git a/text.lua b/text.lua index 5e9317c..79bff98 100644 --- a/text.lua +++ b/text.lua @@ -2,15 +2,13 @@ Text = {} -- draw a line starting from startpos to screen at y between State.left and State.right --- return y for the next line, and position of start of final screen line drawn +-- return y for the next line function Text.draw(State, line_index, y, startpos) --? print('text.draw', line_index, y) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - line_cache.starty = y line_cache.startpos = startpos -- wrap long lines - local final_screen_line_starting_pos = startpos -- track value to return Text.populate_screen_line_starting_pos(State, line_index) assert(#line_cache.screen_line_starting_pos >= 1, 'line cache missing screen line info') for i=1,#line_cache.screen_line_starting_pos do @@ -18,7 +16,6 @@ function Text.draw(State, line_index, y, startpos) if pos < startpos then -- render nothing else - final_screen_line_starting_pos = pos local screen_line = Text.screen_line(line, line_cache, i) --? print('text.draw:', screen_line, 'at', line_index,pos, 'after', x,y) local frag_len = utf8.len(screen_line) @@ -59,7 +56,7 @@ function Text.draw(State, line_index, y, startpos) end end end - return y, final_screen_line_starting_pos + return y end function Text.screen_line(line, line_cache, i) @@ -133,7 +130,7 @@ function Text.text_input(State, t) end end local before = snapshot(State, State.cursor1.line) ---? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.insert_at_cursor(State, t) if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) @@ -165,12 +162,12 @@ function Text.keychord_press(State, chord) record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)}) elseif chord == 'tab' then local before = snapshot(State, State.cursor1.line) ---? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.insert_at_cursor(State, '\t') if State.cursor_y > App.screen.height - State.line_height then Text.populate_screen_line_starting_pos(State, State.cursor1.line) Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right) ---? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('=>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) end schedule_save(State) record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)}) @@ -344,45 +341,64 @@ function Text.insert_return(State) end function Text.pageup(State) ---? print('pageup') + State.screen_top1 = Text.previous_screen_top1(State) + State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} + Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) + Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks +end + +-- return the top y coordinate of a given line_index, +-- or nil if no part of it is on screen +function Text.starty(State, line_index) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + if line_index < State.screen_top1.line then return end + local loc2 = Text.to2(State, State.screen_top1) + local y = State.top + while true do + if loc2.line == line_index then return y end + y = y + State.line_height + if y + State.line_height > App.screen.height then break end + local next_loc2 = Text.next_screen_line(State, loc2) + if Text.eq2(next_loc2, loc2) then break end -- end of file + loc2 = next_loc2 + end +end + +function Text.previous_screen_top1(State) -- duplicate some logic from love.draw - local top2 = Text.to2(State, State.screen_top1) ---? print(App.screen.height) + -- does not modify State (except to populate line_cache) + local loc2 = Text.to2(State, State.screen_top1) local y = App.screen.height - State.line_height while y >= State.top do ---? print(y, top2.line, top2.screen_line, top2.screen_pos) - if State.screen_top1.line == 1 and State.screen_top1.pos == 1 then break end + if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end y = y - State.line_height - top2 = Text.previous_screen_line(State, top2) + loc2 = Text.previous_screen_line(State, loc2) end - State.screen_top1 = Text.to1(State, top2) - State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) ---? print(State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) ---? print('pageup end') + return Text.to1(State, loc2) end function Text.pagedown(State) ---? print('pagedown') - -- If a line/paragraph gets to a page boundary, I often want to scroll - -- before I get to the bottom. - -- However, only do this if it makes forward progress. - local bot2 = Text.to2(State, State.screen_bottom1) - if bot2.screen_line > 1 then - bot2.screen_line = math.max(bot2.screen_line-10, 1) - end - local new_top1 = Text.to1(State, bot2) - if Text.lt1(State.screen_top1, new_top1) then - State.screen_top1 = new_top1 - else - State.screen_top1 = {line=State.screen_bottom1.line, pos=State.screen_bottom1.pos} - end ---? print('setting top to', State.screen_top1.line, State.screen_top1.pos) + State.screen_top1 = Text.screen_bottom1(State) State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State) ---? print('top now', State.screen_top1.line) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks ---? print('pagedown end') +end + +-- return the location of the start of the bottom-most line on screen +function Text.screen_bottom1(State) + -- duplicate some logic from love.draw + -- does not modify State (except to populate line_cache) + local loc2 = Text.to2(State, State.screen_top1) + local y = State.top + while true do + y = y + State.line_height + if y + State.line_height > App.screen.height then break end + local next_loc2 = Text.next_screen_line(State, loc2) + if Text.eq2(next_loc2, loc2) then break end + loc2 = next_loc2 + end + return Text.to1(State, loc2) end function Text.up(State) @@ -424,42 +440,45 @@ function Text.up(State) end function Text.down(State) ---? print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) + print('down', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) assert(State.cursor1.pos, 'cursor has no pos') if Text.cursor_at_final_screen_line(State) then -- line is done, skip to next text line ---? print('cursor at final screen line of its line') + print('cursor at final screen line of its line') if State.cursor1.line < #State.lines then local new_cursor_line = State.cursor1.line+1 State.cursor1.line = new_cursor_line State.cursor1.pos = Text.nearest_cursor_pos(State.font, State.lines[State.cursor1.line].data, State.cursor_x, State.left) ---? print(State.cursor1.pos) + print(State.cursor1.pos) end - if State.cursor1.line > State.screen_bottom1.line then ---? print('screen top before:', State.screen_top1.line, State.screen_top1.pos) ---? print('scroll up preserving cursor') + local screen_bottom1 = Text.screen_bottom1(State) + print('down 2', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, screen_bottom1.line, screen_bottom1.pos) + if State.cursor1.line > screen_bottom1.line then + print('screen top before:', State.screen_top1.line, State.screen_top1.pos) + print('scroll up preserving cursor') Text.snap_cursor_to_bottom_of_screen(State) ---? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) + print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end else -- move down one screen line in current line - local scroll_down = Text.le1(State.screen_bottom1, State.cursor1) ---? print('cursor is NOT at final screen line of its line') + local screen_bottom1 = Text.screen_bottom1(State) + local scroll_down = Text.le1(screen_bottom1, State.cursor1) + print('cursor is NOT at final screen line of its line') local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1) Text.populate_screen_line_starting_pos(State, State.cursor1.line) local new_screen_line_starting_pos = State.line_cache[State.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)) + print('switching pos of screen line at cursor from '..tostring(screen_line_starting_pos)..' to '..tostring(new_screen_line_starting_pos)) local new_screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, new_screen_line_starting_pos) local s = string.sub(State.lines[State.cursor1.line].data, new_screen_line_starting_byte_offset) State.cursor1.pos = new_screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1 ---? print('cursor pos is now', State.cursor1.line, State.cursor1.pos) + print('cursor pos is now', State.cursor1.line, State.cursor1.pos) if scroll_down then ---? print('scroll up preserving cursor') + print('scroll up preserving cursor') Text.snap_cursor_to_bottom_of_screen(State) ---? print('screen top after:', State.screen_top1.line, State.screen_top1.pos) + print('screen top after:', State.screen_top1.line, State.screen_top1.pos) end end ---? print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) + print('=>', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos) end function Text.start_of_line(State) @@ -592,6 +611,14 @@ function Text.pos_at_end_of_screen_line(State, loc1) assert(false, ('invalid pos %d'):format(loc1.pos)) end +function Text.final_text_loc_on_screen(State) + local screen_bottom1 = Text.screen_bottom1(State) + return { + line=screen_bottom1.line, + pos=Text.pos_at_end_of_screen_line(State, screen_bottom1), + } +end + function Text.cursor_at_final_screen_line(State) Text.populate_screen_line_starting_pos(State, State.cursor1.line) local screen_lines = State.line_cache[State.cursor1.line].screen_line_starting_pos @@ -613,7 +640,7 @@ function Text.snap_cursor_to_bottom_of_screen(State) --? print('to2: =>', top2.line, top2.screen_line, top2.screen_pos) -- slide to start of screen line top2.screen_pos = 1 -- start of screen line ---? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('snap', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) --? print('cursor pos '..tostring(State.cursor1.pos)..' is on the #'..tostring(top2.screen_line)..' screen line down') local y = App.screen.height - State.line_height -- duplicate some logic from love.draw @@ -630,26 +657,28 @@ function Text.snap_cursor_to_bottom_of_screen(State) --? print('top2 finally:', top2.line, top2.screen_line, top2.screen_pos) State.screen_top1 = Text.to1(State, top2) --? print('top1 finally:', State.screen_top1.line, State.screen_top1.pos) ---? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos, State.screen_bottom1.line, State.screen_bottom1.pos) +--? print('snap =>', State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos) Text.redraw_all(State) -- if we're scrolling, reclaim all fragments to avoid memory leaks end function Text.in_line(State, line_index, x,y) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - if line_cache.starty == nil then return false end -- outside current page - if y < line_cache.starty then return false end + local starty = Text.starty(State, line_index) + if starty == nil then return false end -- outside current page + if y < starty then return false end Text.populate_screen_line_starting_pos(State, line_index) - return y < line_cache.starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) + return y < starty + State.line_height*(#line_cache.screen_line_starting_pos - Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) + 1) end -- convert mx,my in pixels to schema-1 coordinates function Text.to_pos_on_line(State, line_index, mx, my) local line = State.lines[line_index] local line_cache = State.line_cache[line_index] - assert(my >= line_cache.starty, 'failed to map y pixel to line') + local starty = Text.starty(State, line_index) + assert(my >= starty, 'failed to map y pixel to line') -- duplicate some logic from Text.draw - local y = line_cache.starty + local y = starty local start_screen_line_index = Text.screen_line_index(line_cache.screen_line_starting_pos, line_cache.startpos) for screen_line_index = start_screen_line_index,#line_cache.screen_line_starting_pos do local screen_line_starting_pos = line_cache.screen_line_starting_pos[screen_line_index] @@ -832,6 +861,10 @@ function Text.le1(a, b) return a.pos <= b.pos end +function Text.eq2(a, b) + return a.line == b.line and a.screen_line == b.screen_line and a.screen_pos == b.screen_pos +end + function Text.offset(s, pos1) if pos1 == 1 then return 1 end local result = utf8.offset(s, pos1) @@ -853,6 +886,19 @@ function Text.previous_screen_line(State, loc2) end end +function Text.next_screen_line(State, loc2) + Text.populate_screen_line_starting_pos(State, loc2.line) + if loc2.screen_line >= #State.line_cache[loc2.line].screen_line_starting_pos then + if loc2.line < #State.lines then + return {line=loc2.line+1, screen_line=1, screen_pos=1} + else + return loc2 + end + else + return {line=loc2.line, screen_line=loc2.screen_line+1, screen_pos=1} + end +end + -- resize helper function Text.tweak_screen_top_and_cursor(State) if State.screen_top1.pos == 1 then return end @@ -876,16 +922,12 @@ function Text.tweak_screen_top_and_cursor(State) end end -- make sure cursor is on screen + local screen_bottom1 = Text.screen_bottom1(State) if Text.lt1(State.cursor1, State.screen_top1) then State.cursor1 = {line=State.screen_top1.line, pos=State.screen_top1.pos} - elseif State.cursor1.line >= State.screen_bottom1.line then ---? print('too low') + elseif State.cursor1.line >= screen_bottom1.line then if Text.cursor_out_of_screen(State) then ---? print('tweak') - State.cursor1 = { - line=State.screen_bottom1.line, - pos=Text.to_pos_on_line(State, State.screen_bottom1.line, State.right-5, App.screen.height-5), - } + State.cursor1 = Text.final_text_loc_on_screen(State) end end end @@ -894,11 +936,6 @@ end function Text.cursor_out_of_screen(State) edit.draw(State) return State.cursor_y == nil - -- this approach is cheaper and almost works, except on the final screen - -- where file ends above bottom of screen ---? local botpos = Text.pos_at_start_of_screen_line(State, State.cursor1) ---? local botline1 = {line=State.cursor1.line, pos=botpos} ---? return Text.lt1(State.screen_bottom1, botline1) end function Text.redraw_all(State) |