From f42e932df498f670c9b3d705cbf2f3b3f091c80c Mon Sep 17 00:00:00 2001 From: chentau Date: Fri, 19 Feb 2021 16:28:46 -0800 Subject: [PATCH] Extmarks: Save extmark undo information to undofile. --- src/nvim/undo.c | 114 ++++++++++++++++++-- test/functional/lua/buffer_updates_spec.lua | 54 ++++++++++ 2 files changed, 162 insertions(+), 6 deletions(-) diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8c2ae3bc35..f52850f6f3 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -594,13 +594,20 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) } -# define UF_START_MAGIC "Vim\237UnDo\345" /* magic at start of undofile */ +// magic at start of undofile +# define UF_START_MAGIC "Vim\237UnDo\345" # define UF_START_MAGIC_LEN 9 -# define UF_HEADER_MAGIC 0x5fd0 /* magic at start of header */ -# define UF_HEADER_END_MAGIC 0xe7aa /* magic after last header */ -# define UF_ENTRY_MAGIC 0xf518 /* magic at start of entry */ -# define UF_ENTRY_END_MAGIC 0x3581 /* magic after last entry */ -# define UF_VERSION 2 /* 2-byte undofile version number */ +// magic at start of header +# define UF_HEADER_MAGIC 0x5fd0 +// magic after last header +# define UF_HEADER_END_MAGIC 0xe7aa +// magic at start of entry +# define UF_ENTRY_MAGIC 0xf518 +// magic after last entry +# define UF_ENTRY_END_MAGIC 0x3581 + +// 2-byte undofile version number +# define UF_VERSION 3 /* extra fields for header */ # define UF_LAST_SAVE_NR 1 @@ -847,6 +854,15 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) } } undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + + // Write all extmark undo objects + for (size_t i = 0; i < kv_size(uhp->uh_extmark); i++) { + if (!serialize_extmark(bi, kv_A(uhp->uh_extmark, i))) { + return false; + } + } + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_END_MAGIC, 2); + return true; } @@ -928,9 +944,95 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, return NULL; } + // Unserialize all extmark undo information + ExtmarkUndoObject *extup; + kv_init(uhp->uh_extmark); + + while ((c = undo_read_2c(bi)) == UF_ENTRY_MAGIC) { + bool error = false; + extup = unserialize_extmark(bi, &error, file_name); + if (error) { + kv_destroy(uhp->uh_extmark); + xfree(extup); + return NULL; + } + kv_push(uhp->uh_extmark, *extup); + xfree(extup); + } + if (c != UF_ENTRY_END_MAGIC) { + corruption_error("entry end", file_name); + u_free_uhp(uhp); + return NULL; + } + return uhp; } +static bool serialize_extmark(bufinfo_T *bi, ExtmarkUndoObject extup) +{ + if (extup.type == kExtmarkSplice) { + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + undo_write_bytes(bi, (uintmax_t)extup.type, 4); + if (!undo_write(bi, (uint8_t *)&(extup.data.splice), + sizeof(ExtmarkSplice))) { + return false; + } + } else if (extup.type == kExtmarkMove) { + undo_write_bytes(bi, (uintmax_t)UF_ENTRY_MAGIC, 2); + undo_write_bytes(bi, (uintmax_t)extup.type, 4); + if (!undo_write(bi, (uint8_t *)&(extup.data.move), sizeof(ExtmarkMove))) { + return false; + } + } + // Note: We do not serialize ExtmarkSavePos information, since + // buffer marktrees are not retained when closing/reopening a file + return true; +} + +static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error, + const char *filename) +{ + UndoObjectType type; + uint8_t *buf = NULL; + size_t n_elems; + + ExtmarkUndoObject *extup = xmalloc(sizeof(ExtmarkUndoObject)); + + type = (UndoObjectType)undo_read_4c(bi); + extup->type = type; + if (type == kExtmarkSplice) { + n_elems = (size_t)sizeof(ExtmarkSplice) / sizeof(uint8_t); + buf = xcalloc(sizeof(uint8_t), n_elems); + if (!undo_read(bi, buf, n_elems)) { + goto error; + } + extup->data.splice = *(ExtmarkSplice *)buf; + } else if (type == kExtmarkMove) { + n_elems = (size_t)sizeof(ExtmarkMove) / sizeof(uint8_t); + buf = xcalloc(sizeof(uint8_t), n_elems); + if (!undo_read(bi, buf, n_elems)) { + goto error; + } + extup->data.move = *(ExtmarkMove *)buf; + } else { + goto error; + } + + if (buf) { + xfree(buf); + } + + return extup; + +error: + xfree(extup); + if (buf) { + xfree(buf); + } + *error = true; + return NULL; +} + /// Serializes "uep". /// /// @param bi The buffer information diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index f38c0e8b09..db268ea707 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -658,8 +658,62 @@ describe('lua: nvim_buf_attach on_bytes', function() } end) + it("sends events when undoing with undofile", function() + write_file("Xtest-undofile", dedent([[ + 12345 + hello world + ]])) + + command("e! Xtest-undofile") + command("set undodir=. | set undofile") + + local ns = helpers.request('nvim_create_namespace', "ns1") + meths.buf_set_extmark(0, ns, 0, 0, {}) + + eq({"12345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + -- splice + feed("gg0d2l") + + eq({"345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + -- move + command(".m+1") + + eq({"hello world", "345"}, meths.buf_get_lines(0, 0, -1, true)) + + -- reload undofile and undo changes + command("w") + command("set noundofile") + command("bw!") + command("e! Xtest-undofile") + + command("set undofile") + + local check_events = setup_eventcheck(verify, nil) + + feed("u") + eq({"345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + check_events { + { "test1", "bytes", 2, 6, 1, 0, 12, 1, 0, 4, 0, 0, 0 }, + { "test1", "bytes", 2, 6, 0, 0, 0, 0, 0, 0, 1, 0, 4 } + } + + feed("u") + eq({"12345", "hello world"}, meths.buf_get_lines(0, 0, -1, true)) + + check_events { + { "test1", "bytes", 2, 8, 0, 0, 0, 0, 0, 0, 0, 2, 2 } + } + command("bw!") + end) + + teardown(function() os.remove "Xtest-reload" + os.remove "Xtest-undofile" + os.remove ".Xtest-undofile.un~" end) end