aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormitchell <70453897+orbitalquark@users.noreply.github.com>2021-04-11 22:51:59 -0400
committermitchell <70453897+orbitalquark@users.noreply.github.com>2021-04-11 22:51:59 -0400
commit1e693f06a6556b87ed4f56a3635a0c10640b1f92 (patch)
tree80c2fd9581119cd02fb54f558dfbe3634f231c8c
parentfe3d84c16252a65c117e26b873668ec967e96b4d (diff)
downloadtextadept-1e693f06a6556b87ed4f56a3635a0c10640b1f92.tar.gz
textadept-1e693f06a6556b87ed4f56a3635a0c10640b1f92.zip
Save/restore view state when undoing/redoing full-buffer changes.
For example external code formatting commands that replace buffer contents.
-rw-r--r--modules/textadept/history.lua21
-rw-r--r--test/test.lua27
2 files changed, 45 insertions, 3 deletions
diff --git a/modules/textadept/history.lua b/modules/textadept/history.lua
index da9f5885..05081ddd 100644
--- a/modules/textadept/history.lua
+++ b/modules/textadept/history.lua
@@ -33,20 +33,35 @@ local view_history = setmetatable({}, {
end
})
+local restore_position, first_visible_line = false, nil
+-- Restore position after a full-buffer undo/redo operation, e.g. after replacing buffer contents
+-- with a formatting command and then performing an undo.
+events.connect(events.UPDATE_UI, function(updated)
+ if not restore_position or updated & buffer.UPDATE_SELECTION == 0 then return end
+ restore_position = false
+ M.back()
+ view.first_visible_line, first_visible_line = first_visible_line, nil
+end)
+
-- Listens for text insertion and deletion events and records their locations.
events.connect(events.MODIFIED, function(position, mod, text, length)
local buffer = buffer
-- Only interested in text insertion or deletion.
if mod & buffer.MOD_INSERTTEXT > 0 then
- if length == buffer.length then return end -- ignore file loading
+ if length == buffer.length then
+ if mod & buffer.MULTILINEUNDOREDO > 0 then restore_position = true end
+ return -- ignore file loading or replacing buffer contents
+ end
position = position + length
elseif mod & buffer.MOD_DELETETEXT > 0 then
if buffer.length == 0 then return end -- ignore replacing buffer contents
+ elseif mod & (buffer.PERFORMED_UNDO | buffer.PERFORMED_REDO) > 0 and
+ (mod & buffer.MOD_BEFOREDELETE > 0) and length == buffer.length then
+ first_visible_line = view.first_visible_line -- save for potential undo before it's lost
else
return
end
- -- Ignore undo/redo.
- if mod & (buffer.PERFORMED_UNDO | buffer.PERFORMED_REDO) > 0 then return end
+ if mod & (buffer.PERFORMED_UNDO | buffer.PERFORMED_REDO) > 0 then return end -- ignore undo/redo
M.record(nil, buffer:line_from_position(position), buffer.column[position])
end)
diff --git a/test/test.lua b/test/test.lua
index e9c6bf75..4e6b5441 100644
--- a/test/test.lua
+++ b/test/test.lua
@@ -2932,6 +2932,33 @@ function test_history_print_buffer()
ui.tabs = tabs -- restore
end
+function test_history_undo_full_buffer_change()
+ buffer.new()
+ local lines = {}
+ for i = 99, 1, -1 do lines[#lines + 1] = tostring(i) end
+ buffer:add_text(table.concat(lines, '\n'))
+ buffer:goto_line(50)
+ buffer:add_text('1')
+ textadept.editing.filter_through('sort -n')
+ ui.update()
+ assert(buffer:get_line(buffer:line_from_position(buffer.current_pos)) ~= '150\n', 'not sorted')
+ local first_visible_line = view.first_visible_line
+ buffer:undo()
+ -- Verify the view state was restored.
+ ui.update()
+ if CURSES then events.emit(events.UPDATE_UI, buffer.UPDATE_SELECTION) end
+ assert_equal(buffer:line_from_position(buffer.current_pos), 50)
+ assert_equal(buffer:get_line(buffer:line_from_position(buffer.current_pos)), '150\n')
+ assert_equal(view.first_visible_line, first_visible_line)
+ buffer:redo()
+ -- Verify the previous view state was kept.
+ ui.update()
+ if CURSES then events.emit(events.UPDATE_UI, buffer.UPDATE_SELECTION) end
+ assert_equal(buffer:line_from_position(buffer.current_pos), 50)
+ assert_equal(view.first_visible_line, first_visible_line)
+ buffer:close(true)
+end
+
function test_macro_record_play_save_load()
textadept.macros.save() -- should not do anything
textadept.macros.play() -- should not do anything