about summary refs log tree commit diff stats
path: root/text.lua
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2024-06-11 12:13:18 -0700
committerKartik K. Agaram <vc@akkartik.com>2024-06-11 12:13:18 -0700
commit262c078133e1603aa30b3404f4f4b12428d925e5 (patch)
tree8101bf50db1523f2fc1c4f8d8f6ca7500355ff10 /text.lua
parenta244738b01a2f471fc5bb9ac04421aaa17c6e66c (diff)
parent55f5c2d696ffba6c5d220d8e317c15ed2335ac7b (diff)
downloadview.love-262c078133e1603aa30b3404f4f4b12428d925e5.tar.gz
Merge lines.love
Diffstat (limited to 'text.lua')
-rw-r--r--text.lua175
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)