about summary refs log tree commit diff stats
path: root/text.lua
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2022-06-02 15:45:25 -0700
committerKartik K. Agaram <vc@akkartik.com>2022-06-02 15:45:25 -0700
commit670886240f78314b9925e94e1814a8ade5fbaf3e (patch)
tree9e52827ab930246aa88576007903a0fa11dbafb7 /text.lua
parenta9a133e6fb80460ab364e66206a7ffc8f9404b46 (diff)
downloadtext.love-670886240f78314b9925e94e1814a8ade5fbaf3e.tar.gz
after much struggle, a brute-force undo
Incredibly inefficient, but I don't yet know how to efficiently encode
undo mutations that can span multiple lines.

There seems to be one bug related to creating new drawings; they're not
spawning events and undoing past drawing creation has some weird
artifacts. Redo seems to consistently work, though.
Diffstat (limited to 'text.lua')
-rw-r--r--text.lua125
1 files changed, 125 insertions, 0 deletions
diff --git a/text.lua b/text.lua
index d86e2bc..b35de4e 100644
--- a/text.lua
+++ b/text.lua
@@ -3,6 +3,8 @@ Text = {}
 
 local utf8 = require 'utf8'
 
+require 'undo'
+
 -- return values:
 --  y coordinate drawn until in px
 --  position of start of final screen line drawn
@@ -1091,6 +1093,83 @@ function test_backspace_to_start_of_line()
   check_nil(Selection1.line, "F - test_backspace_to_start_of_line/selection")
 end
 
+function test_undo_insert_text()
+  io.write('\ntest_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('\ntest_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
+
 function Text.compute_fragments(line, line_width)
 --?   print('compute_fragments', line_width)
   line.fragments = {}
@@ -1142,6 +1221,8 @@ end
 
 function Text.insert_at_cursor(t)
   if Selection1.line then Text.delete_selection() end
+  -- Collect what you did in an event that can be undone.
+  local before = snapshot_everything()
   local byte_offset
   if Cursor1.pos > 1 then
     byte_offset = utf8.offset(Lines[Cursor1.line].data, Cursor1.pos)
@@ -1152,6 +1233,8 @@ function Text.insert_at_cursor(t)
   Lines[Cursor1.line].fragments = nil
   Lines[Cursor1.line].screen_line_starting_pos = nil
   Cursor1.pos = Cursor1.pos+1
+  -- finalize undo event
+  record_undo_event({before=before, after=snapshot_everything()})
 end
 
 -- Don't handle any keys here that would trigger love.textinput above.
@@ -1159,6 +1242,7 @@ function Text.keychord_pressed(chord)
 --?   print(chord)
   --== shortcuts that mutate text
   if chord == 'return' then
+    local before = snapshot_everything()
     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)})
     local scroll_down = (Cursor_y + math.floor(15*Zoom)) > App.screen.height
@@ -1171,12 +1255,18 @@ function Text.keychord_pressed(chord)
       Screen_top1.line = Cursor1.line
       Text.scroll_up_while_cursor_on_screen()
     end
+    record_undo_event({before=before, after=snapshot_everything()})
   elseif chord == 'tab' then
+    local before = snapshot_everything()
     Text.insert_at_cursor('\t')
     save_to_disk(Lines, Filename)
+    record_undo_event({before=before, after=snapshot_everything()})
   elseif chord == 'backspace' then
+    local before = snapshot_everything()
     if Selection1.line then
       Text.delete_selection()
+      save_to_disk(Lines, Filename)
+      record_undo_event({before=before, after=snapshot_everything()})
       return
     end
     if Cursor1.pos > 1 then
@@ -1210,9 +1300,13 @@ function Text.keychord_pressed(chord)
     end
     assert(Text.le1(Screen_top1, Cursor1))
     save_to_disk(Lines, Filename)
+    record_undo_event({before=before, after=snapshot_everything()})
   elseif chord == 'delete' then
+    local before = snapshot_everything()
     if Selection1.line then
       Text.delete_selection()
+      save_to_disk(Lines, Filename)
+      record_undo_event({before=before, after=snapshot_everything()})
       return
     end
     if Cursor1.pos <= utf8.len(Lines[Cursor1.line].data) then
@@ -1238,6 +1332,37 @@ function Text.keychord_pressed(chord)
       end
     end
     save_to_disk(Lines, Filename)
+    record_undo_event({before=before, after=snapshot_everything()})
+  -- 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
   -- paste
   elseif chord == 'M-c' then
     local s = Text.selection()