about summary refs log tree commit diff stats
path: root/text.lua
diff options
context:
space:
mode:
Diffstat (limited to 'text.lua')
-rw-r--r--text.lua222
1 files changed, 58 insertions, 164 deletions
diff --git a/text.lua b/text.lua
index e50b154..dd5d50c 100644
--- a/text.lua
+++ b/text.lua
@@ -47,7 +47,7 @@ function Text.draw(State, line_index, y, startpos)
           end
         end
       end
-      -- render fragment
+      -- render screen line
       App.color(Text_color)
       App.screen.print(screen_line, State.left,y)
       y = y + State.line_height
@@ -82,7 +82,6 @@ end
 
 function Text.populate_screen_line_starting_pos(State, line_index)
   local line = State.lines[line_index]
-  if line.mode ~= 'text' then return end
   local line_cache = State.line_cache[line_index]
   if line_cache.screen_line_starting_pos then
     return
@@ -124,7 +123,7 @@ function Text.text_input(State, t)
   if App.mouse_down(1) then return end
   if App.any_modifier_down() then
     if App.key_down(t) then
-      -- The modifiers didn't change the key. Handle it in keychord_pressed.
+      -- The modifiers didn't change the key. Handle it in keychord_press.
       return
     else
       -- Key mutated by the keyboard layout. Continue below.
@@ -141,7 +140,6 @@ function Text.text_input(State, t)
 end
 
 function Text.insert_at_cursor(State, t)
-  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
   local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
   State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)..t..string.sub(State.lines[State.cursor1.line].data, byte_offset)
   Text.clear_screen_line_cache(State, State.cursor1.line)
@@ -151,17 +149,16 @@ end
 -- Don't handle any keys here that would trigger text_input above.
 function Text.keychord_press(State, chord)
 --?   print('chord', chord, State.selection1.line, State.selection1.pos)
-  --== shortcuts that mutate text
+  --== shortcuts that mutate text (must schedule_save)
   if chord == 'return' then
     local before_line = State.cursor1.line
     local before = snapshot(State, before_line)
     Text.insert_return(State)
-    State.selection1 = {}
     if State.cursor_y > App.screen.height - State.line_height then
       Text.snap_cursor_to_bottom_of_screen(State, State.left, State.right)
     end
-    schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, before_line, State.cursor1.line)})
+    schedule_save(State)
   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)
@@ -171,8 +168,8 @@ function Text.keychord_press(State, chord)
       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)
     end
-    schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
+    schedule_save(State)
   elseif chord == 'backspace' then
     if State.selection1.line then
       Text.delete_selection(State, State.left, State.right)
@@ -194,16 +191,11 @@ function Text.keychord_press(State, chord)
       end
     elseif State.cursor1.line > 1 then
       before = snapshot(State, State.cursor1.line-1, State.cursor1.line)
-      if State.lines[State.cursor1.line-1].mode == 'drawing' then
-        table.remove(State.lines, State.cursor1.line-1)
-        table.remove(State.line_cache, State.cursor1.line-1)
-      else
-        -- join lines
-        State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
-        State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
-        table.remove(State.lines, State.cursor1.line)
-        table.remove(State.line_cache, State.cursor1.line)
-      end
+      -- join lines
+      State.cursor1.pos = utf8.len(State.lines[State.cursor1.line-1].data)+1
+      State.lines[State.cursor1.line-1].data = State.lines[State.cursor1.line-1].data..State.lines[State.cursor1.line].data
+      table.remove(State.lines, State.cursor1.line)
+      table.remove(State.line_cache, State.cursor1.line)
       State.cursor1.line = State.cursor1.line-1
     end
     if State.screen_top1.line > #State.lines then
@@ -215,12 +207,12 @@ function Text.keychord_press(State, chord)
         line=State.cursor1.line,
         pos=Text.pos_at_start_of_screen_line(State, State.cursor1),
       }
-      Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
+      Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
     end
     Text.clear_screen_line_cache(State, State.cursor1.line)
     assert(Text.le1(State.screen_top1, State.cursor1), ('screen_top (line=%d,pos=%d) is below cursor (line=%d,pos=%d)'):format(State.screen_top1.line, State.screen_top1.pos, State.cursor1.line, State.cursor1.pos))
-    schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
+    schedule_save(State)
   elseif chord == 'delete' then
     if State.selection1.line then
       Text.delete_selection(State, State.left, State.right)
@@ -245,16 +237,14 @@ function Text.keychord_press(State, chord)
         -- no change to State.cursor1.pos
       end
     elseif State.cursor1.line < #State.lines then
-      if State.lines[State.cursor1.line+1].mode == 'text' then
-        -- join lines
-        State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
-      end
+      -- join lines
+      State.lines[State.cursor1.line].data = State.lines[State.cursor1.line].data..State.lines[State.cursor1.line+1].data
       table.remove(State.lines, State.cursor1.line+1)
       table.remove(State.line_cache, State.cursor1.line+1)
     end
     Text.clear_screen_line_cache(State, State.cursor1.line)
-    schedule_save(State)
     record_undo_event(State, {before=before, after=snapshot(State, State.cursor1.line)})
+    schedule_save(State)
   --== shortcuts that move the cursor
   elseif chord == 'left' then
     Text.left(State)
@@ -342,7 +332,7 @@ end
 
 function Text.insert_return(State)
   local byte_offset = Text.offset(State.lines[State.cursor1.line].data, State.cursor1.pos)
-  table.insert(State.lines, State.cursor1.line+1, {mode='text', data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})
+  table.insert(State.lines, State.cursor1.line+1, {data=string.sub(State.lines[State.cursor1.line].data, byte_offset)})
   table.insert(State.line_cache, State.cursor1.line+1, {})
   State.lines[State.cursor1.line].data = string.sub(State.lines[State.cursor1.line].data, 1, byte_offset-1)
   Text.clear_screen_line_cache(State, State.cursor1.line)
@@ -353,7 +343,7 @@ function Text.pageup(State)
   State.screen_top1 = Text.previous_screen_top1(State)
   State.cursor1 = deepcopy(State.screen_top1)
   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
+  Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
 end
 
 -- return the top y coordinate of a given line_index,
@@ -365,15 +355,8 @@ function Text.starty(State, line_index)
   local loc2 = Text.to2(State, State.screen_top1)
   local y = State.top
   while true do
-    if State.lines[loc2.line].mode == 'drawing' then
-      y = y + Drawing_padding_top
-    end
     if loc2.line == line_index then return y end
-    if State.lines[loc2.line].mode == 'text' then
-      y = y + State.line_height
-    elseif State.lines[loc2.line].mode == 'drawing' then
-      y = y + Drawing.pixels(State.lines[loc2.line].h, State.width) + Drawing_padding_bottom
-    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
@@ -388,11 +371,7 @@ function Text.previous_screen_top1(State)
   local y = App.screen.height - State.line_height
   while y >= State.top do
     if loc2.line == 1 and loc2.screen_line == 1 and loc2.screen_pos == 1 then break end
-    if State.lines[loc2.line].mode == 'text' then
-      y = y - State.line_height
-    elseif State.lines[loc2.line].mode == 'drawing' then
-      y = y - Drawing_padding_height - Drawing.pixels(State.lines[loc2.line].h, State.width)
-    end
+    y = y - State.line_height
     loc2 = Text.previous_screen_line(State, loc2)
   end
   return Text.to1(State, loc2)
@@ -402,7 +381,7 @@ function Text.pagedown(State)
   State.screen_top1 = Text.screen_bottom1(State)
   State.cursor1 = deepcopy(State.screen_top1)
   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
+  Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
 end
 
 -- return the location of the start of the bottom-most line on screen
@@ -412,11 +391,7 @@ function Text.screen_bottom1(State)
   local loc2 = Text.to2(State, State.screen_top1)
   local y = State.top
   while true do
-    if State.lines[loc2.line].mode == 'text' then
-      y = y + State.line_height
-    elseif State.lines[loc2.line].mode == 'drawing' then
-      y = y + Drawing_padding_height + Drawing.pixels(State.lines[loc2.line].h, State.width)
-    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
@@ -426,29 +401,24 @@ function Text.screen_bottom1(State)
 end
 
 function Text.up(State)
-  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
 --?   print('up', State.cursor1.line, State.cursor1.pos, State.screen_top1.line, State.screen_top1.pos)
   local screen_line_starting_pos, screen_line_index = Text.pos_at_start_of_screen_line(State, State.cursor1)
   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 = State.cursor1.line
-    while new_cursor_line > 1 do
-      new_cursor_line = new_cursor_line-1
-      if State.lines[new_cursor_line].mode == 'text' then
---?         print('found previous text line')
-        State.cursor1 = {line=new_cursor_line, pos=nil}
-        Text.populate_screen_line_starting_pos(State, State.cursor1.line)
-        -- previous text line found, pick its final screen line
---?         print('has multiple screen lines')
-        local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
---?         print(#screen_line_starting_pos)
-        screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
-        local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
-        local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
-        State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1
-        break
-      end
+    if State.cursor1.line > 1 then
+      local new_cursor_line = State.cursor1.line-1
+--?       print('found previous text line')
+      State.cursor1 = {line=new_cursor_line, pos=nil}
+      Text.populate_screen_line_starting_pos(State, State.cursor1.line)
+      -- previous text line found, pick its final screen line
+--?       print('has multiple screen lines')
+      local screen_line_starting_pos = State.line_cache[State.cursor1.line].screen_line_starting_pos
+--?       print(#screen_line_starting_pos)
+      screen_line_starting_pos = screen_line_starting_pos[#screen_line_starting_pos]
+      local screen_line_starting_byte_offset = Text.offset(State.lines[State.cursor1.line].data, screen_line_starting_pos)
+      local s = string.sub(State.lines[State.cursor1.line].data, screen_line_starting_byte_offset)
+      State.cursor1.pos = screen_line_starting_pos + Text.nearest_cursor_pos(State.font, s, State.cursor_x, State.left) - 1
     end
   else
     -- move up one screen line in current line
@@ -464,28 +434,21 @@ function Text.up(State)
       line=State.cursor1.line,
       pos=Text.pos_at_start_of_screen_line(State, State.cursor1),
     }
-    Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
+    Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
   end
 end
 
 function Text.down(State)
-  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
 --?   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')
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line < #State.lines do
-      new_cursor_line = new_cursor_line+1
-      if State.lines[new_cursor_line].mode == 'text' then
-        State.cursor1 = {
-          line = new_cursor_line,
-          pos = Text.nearest_cursor_pos(State.font, State.lines[new_cursor_line].data, State.cursor_x, State.left),
-        }
---?         print(State.cursor1.pos)
-        break
-      end
+    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)
     end
     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)
@@ -589,28 +552,18 @@ function Text.match(s, pos, pat)
 end
 
 function Text.left(State)
-  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
   if State.cursor1.pos > 1 then
     State.cursor1.pos = State.cursor1.pos-1
-  else
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line > 1 do
-      new_cursor_line = new_cursor_line-1
-      if State.lines[new_cursor_line].mode == 'text' then
-        State.cursor1 = {
-          line = new_cursor_line,
-          pos = utf8.len(State.lines[new_cursor_line].data) + 1,
-        }
-        break
-      end
-    end
+  elseif State.cursor1.line > 1 then
+    State.cursor1.line = State.cursor1.line-1
+    State.cursor1.pos = utf8.len(State.lines[State.cursor1.line].data) + 1
   end
   if Text.lt1(State.cursor1, State.screen_top1) then
     State.screen_top1 = {
       line=State.cursor1.line,
       pos=Text.pos_at_start_of_screen_line(State, State.cursor1),
     }
-    Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
+    Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
   end
 end
 
@@ -622,18 +575,11 @@ function Text.right(State)
 end
 
 function Text.right_without_scroll(State)
-  assert(State.lines[State.cursor1.line].mode == 'text', 'line is not text')
   if State.cursor1.pos <= utf8.len(State.lines[State.cursor1.line].data) then
     State.cursor1.pos = State.cursor1.pos+1
-  else
-    local new_cursor_line = State.cursor1.line
-    while new_cursor_line <= #State.lines-1 do
-      new_cursor_line = new_cursor_line+1
-      if State.lines[new_cursor_line].mode == 'text' then
-        State.cursor1 = {line=new_cursor_line, pos=1}
-        break
-      end
-    end
+  elseif State.cursor1.line <= #State.lines-1 then
+    State.cursor1.line = State.cursor1.line+1
+    State.cursor1.pos = 1
   end
 end
 
@@ -651,7 +597,6 @@ function Text.pos_at_start_of_screen_line(State, loc1)
 end
 
 function Text.pos_at_end_of_screen_line(State, loc1)
-  assert(State.lines[loc1.line].mode == 'text')
   Text.populate_screen_line_starting_pos(State, loc1.line)
   local line_cache = State.line_cache[loc1.line]
   local most_recent_final_pos = utf8.len(State.lines[loc1.line].data)+1
@@ -667,21 +612,10 @@ end
 
 function Text.final_text_loc_on_screen(State)
   local screen_bottom1 = Text.screen_bottom1(State)
-  if State.lines[screen_bottom1.line].mode == 'text' then
-    return {
-      line=screen_bottom1.line,
-      pos=Text.pos_at_end_of_screen_line(State, screen_bottom1),
-    }
-  end
-  local loc2 = Text.to2(State, screen_bottom1)
-  while true do
-    if State.lines[loc2.line].mode == 'text' then break end
-    assert(loc2.line > 1 or loc2.screen_line > 1 and loc2.screen_pos > 1)  -- elsewhere we're making sure there's always at least one text line on screen
-    loc2 = Text.previous_screen_line(State, loc2)
-  end
-  local result = Text.to1(State, loc2)
-  result.pos = Text.pos_at_end_of_screen_line(State, result)
-  return result
+  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)
@@ -692,26 +626,7 @@ function Text.cursor_at_final_screen_line(State)
 end
 
 function Text.move_cursor_down_to_next_text_line_while_scrolling_again_if_necessary(State)
-  local y = State.top
-  while State.cursor1.line <= #State.lines do
-    if State.lines[State.cursor1.line].mode == 'text' then
-      break
-    end
---?     print('cursor skips', State.cursor1.line)
-    y = y + Drawing_padding_height + Drawing.pixels(State.lines[State.cursor1.line].h, State.width)
-    State.cursor1.line = State.cursor1.line + 1
-  end
-  if State.cursor1.pos == nil then
-    State.cursor1.pos = 1
-  end
-  -- hack: insert a text line at bottom of file if necessary
-  if State.cursor1.line > #State.lines then
-    assert(State.cursor1.line == #State.lines+1, 'tried to ensure bottom line of file is text, but failed')
-    table.insert(State.lines, {mode='text', data=''})
-    table.insert(State.line_cache, {})
-  end
---?   print(y, App.screen.height, App.screen.height-State.line_height)
-  if y > App.screen.height - State.line_height then
+  if State.top > App.screen.height - State.line_height then
 --?     print('scroll up')
     Text.snap_cursor_to_bottom_of_screen(State)
   end
@@ -731,31 +646,18 @@ function Text.snap_cursor_to_bottom_of_screen(State)
   while true do
 --?     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 State.lines[top2.line-1].mode == 'text' then
-      local h = State.line_height
-      if y - h < State.top then
-        break
-      end
-      y = y - h
-    else
-      assert(top2.line > 1, 'tried to snap cursor to buttom of screen but failed')
-      assert(State.lines[top2.line-1].mode == 'drawing', "expected a drawing but it's not")
-      -- We currently can't draw partial drawings, so either skip it entirely
-      -- or not at all.
-      local h = Drawing_padding_height + Drawing.pixels(State.lines[top2.line-1].h, State.width)
-      if y - h < State.top then
-        break
-      end
---?       print('skipping drawing of height', h)
-      y = y - h
+    local h = State.line_height
+    if y - h < State.top then
+      break
     end
+    y = y - h
     top2 = Text.previous_screen_line(State, top2)
   end
 --?   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)
-  Text.redraw_all(State)  -- if we're scrolling, reclaim all fragments to avoid memory leaks
+  Text.redraw_all(State)  -- if we're scrolling, reclaim all line caches to avoid memory leaks
 end
 
 function Text.in_line(State, line_index, x,y)
@@ -911,9 +813,6 @@ function Text.x(font, s, pos)
 end
 
 function Text.to2(State, loc1)
-  if State.lines[loc1.line].mode == 'drawing' then
-    return {line=loc1.line, screen_line=1, screen_pos=1}
-  end
   local result = {line=loc1.line}
   local line_cache = State.line_cache[loc1.line]
   Text.populate_screen_line_starting_pos(State, loc1.line)
@@ -979,8 +878,6 @@ function Text.previous_screen_line(State, loc2)
     return {line=loc2.line, screen_line=loc2.screen_line-1, screen_pos=1}
   elseif loc2.line == 1 then
     return loc2
-  elseif State.lines[loc2.line-1].mode == 'drawing' then
-    return {line=loc2.line-1, screen_line=1, screen_pos=1}
   else
     local l = State.lines[loc2.line-1]
     Text.populate_screen_line_starting_pos(State, loc2.line-1)
@@ -989,9 +886,6 @@ function Text.previous_screen_line(State, loc2)
 end
 
 function Text.next_screen_line(State, loc2)
-  if State.lines[loc2.line].mode == 'drawing' then
-    return {line=loc2.line+1, screen_line=1, screen_pos=1}
-  end
   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
@@ -1044,7 +938,7 @@ function Text.cursor_out_of_screen(State)
 end
 
 function Text.redraw_all(State)
---?   print('clearing fragments')
+--?   print('clearing line caches')
   -- Perform some early sanity checking here, in hopes that we correctly call
   -- this whenever we change editor state.
   if State.right <= State.left then