aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/undo.c
diff options
context:
space:
mode:
authorEliseo Martínez <eliseomarmol@gmail.com>2014-05-12 02:25:17 +0200
committerEliseo Martínez <eliseomarmol@gmail.com>2014-05-15 20:46:01 +0200
commitda51dc9cf202772f60bd2da975dbef257bd9237c (patch)
tree5c16b93238a153f55634e9323077f30c8133970c /src/nvim/undo.c
parentffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff)
downloadrneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.gz
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.tar.bz2
rneovim-da51dc9cf202772f60bd2da975dbef257bd9237c.zip
Introduce nvim namespace: Move files.
Move files from src/ to src/nvim/. - src/nvim/ becomes the new root dir for nvim executable sources. - src/libnvim/ is planned to become root dir of the neovim library.
Diffstat (limited to 'src/nvim/undo.c')
-rw-r--r--src/nvim/undo.c2880
1 files changed, 2880 insertions, 0 deletions
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
new file mode 100644
index 0000000000..47ce8ee8da
--- /dev/null
+++ b/src/nvim/undo.c
@@ -0,0 +1,2880 @@
+/*
+ * VIM - Vi IMproved by Bram Moolenaar
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * undo.c: multi level undo facility
+ *
+ * The saved lines are stored in a list of lists (one for each buffer):
+ *
+ * b_u_oldhead------------------------------------------------+
+ * |
+ * V
+ * +--------------+ +--------------+ +--------------+
+ * b_u_newhead--->| u_header | | u_header | | u_header |
+ * | uh_next------>| uh_next------>| uh_next---->NULL
+ * NULL<--------uh_prev |<---------uh_prev |<---------uh_prev |
+ * | uh_entry | | uh_entry | | uh_entry |
+ * +--------|-----+ +--------|-----+ +--------|-----+
+ * | | |
+ * V V V
+ * +--------------+ +--------------+ +--------------+
+ * | u_entry | | u_entry | | u_entry |
+ * | ue_next | | ue_next | | ue_next |
+ * +--------|-----+ +--------|-----+ +--------|-----+
+ * | | |
+ * V V V
+ * +--------------+ NULL NULL
+ * | u_entry |
+ * | ue_next |
+ * +--------|-----+
+ * |
+ * V
+ * etc.
+ *
+ * Each u_entry list contains the information for one undo or redo.
+ * curbuf->b_u_curhead points to the header of the last undo (the next redo),
+ * or is NULL if nothing has been undone (end of the branch).
+ *
+ * For keeping alternate undo/redo branches the uh_alt field is used. Thus at
+ * each point in the list a branch may appear for an alternate to redo. The
+ * uh_seq field is numbered sequentially to be able to find a newer or older
+ * branch.
+ *
+ * +---------------+ +---------------+
+ * b_u_oldhead --->| u_header | | u_header |
+ * | uh_alt_next ---->| uh_alt_next ----> NULL
+ * NULL <----- uh_alt_prev |<------ uh_alt_prev |
+ * | uh_prev | | uh_prev |
+ * +-----|---------+ +-----|---------+
+ * | |
+ * V V
+ * +---------------+ +---------------+
+ * | u_header | | u_header |
+ * | uh_alt_next | | uh_alt_next |
+ * b_u_newhead --->| uh_alt_prev | | uh_alt_prev |
+ * | uh_prev | | uh_prev |
+ * +-----|---------+ +-----|---------+
+ * | |
+ * V V
+ * NULL +---------------+ +---------------+
+ * | u_header | | u_header |
+ * | uh_alt_next ---->| uh_alt_next |
+ * | uh_alt_prev |<------ uh_alt_prev |
+ * | uh_prev | | uh_prev |
+ * +-----|---------+ +-----|---------+
+ * | |
+ * etc. etc.
+ *
+ *
+ * All data is allocated and will all be freed when the buffer is unloaded.
+ */
+
+/* Uncomment the next line for including the u_check() function. This warns
+ * for errors in the debug information. */
+/* #define U_DEBUG 1 */
+#define UH_MAGIC 0x18dade /* value for uh_magic when in use */
+#define UE_MAGIC 0xabc123 /* value for ue_magic when in use */
+
+#include <string.h>
+
+#include "vim.h"
+#include "undo.h"
+#include "edit.h"
+#include "eval.h"
+#include "fileio.h"
+#include "fold.h"
+#include "mark.h"
+#include "memline.h"
+#include "message.h"
+#include "misc1.h"
+#include "misc2.h"
+#include "memory.h"
+#include "crypt.h"
+#include "garray.h"
+#include "option.h"
+#include "os_unix.h"
+#include "path.h"
+#include "quickfix.h"
+#include "screen.h"
+#include "sha256.h"
+#include "os/os.h"
+#include "os/time.h"
+
+static long get_undolevel(void);
+static void u_unch_branch(u_header_T *uhp);
+static u_entry_T *u_get_headentry(void);
+static void u_getbot(void);
+static void u_doit(int count);
+static void u_undoredo(int undo);
+static void u_undo_end(int did_undo, int absolute);
+static void u_add_time(char_u *buf, size_t buflen, time_t tt);
+static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp);
+static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp);
+static void u_freeentries(buf_T *buf, u_header_T *uhp,
+ u_header_T **uhpp);
+static void u_freeentry(u_entry_T *, long);
+static void corruption_error(char *mesg, char_u *file_name);
+static void u_free_uhp(u_header_T *uhp);
+static size_t fwrite_crypt(buf_T *buf, char_u *ptr, size_t len,
+ FILE *fp);
+static char_u *read_string_decrypt(buf_T *buf, FILE *fd, int len);
+static int serialize_header(FILE *fp, buf_T *buf, char_u *hash);
+static int serialize_uhp(FILE *fp, buf_T *buf, u_header_T *uhp);
+static u_header_T *unserialize_uhp(FILE *fp, char_u *file_name);
+static int serialize_uep(FILE *fp, buf_T *buf, u_entry_T *uep);
+static u_entry_T *unserialize_uep(FILE *fp, int *error,
+ char_u *file_name);
+static void serialize_pos(pos_T pos, FILE *fp);
+static void unserialize_pos(pos_T *pos, FILE *fp);
+static void serialize_visualinfo(visualinfo_T *info, FILE *fp);
+static void unserialize_visualinfo(visualinfo_T *info, FILE *fp);
+static void put_header_ptr(FILE *fp, u_header_T *uhp);
+
+static char_u *u_save_line(linenr_T);
+
+/* used in undo_end() to report number of added and deleted lines */
+static long u_newcount, u_oldcount;
+
+/*
+ * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember
+ * the action that "u" should do.
+ */
+static int undo_undoes = FALSE;
+
+static int lastmark = 0;
+
+#if defined(U_DEBUG) || defined(PROTO)
+/*
+ * Check the undo structures for being valid. Print a warning when something
+ * looks wrong.
+ */
+static int seen_b_u_curhead;
+static int seen_b_u_newhead;
+static int header_count;
+
+static void u_check_tree(u_header_T *uhp,
+ u_header_T *exp_uh_next,
+ u_header_T *exp_uh_alt_prev) {
+ u_entry_T *uep;
+
+ if (uhp == NULL)
+ return;
+ ++header_count;
+ if (uhp == curbuf->b_u_curhead && ++seen_b_u_curhead > 1) {
+ EMSG("b_u_curhead found twice (looping?)");
+ return;
+ }
+ if (uhp == curbuf->b_u_newhead && ++seen_b_u_newhead > 1) {
+ EMSG("b_u_newhead found twice (looping?)");
+ return;
+ }
+
+ if (uhp->uh_magic != UH_MAGIC)
+ EMSG("uh_magic wrong (may be using freed memory)");
+ else {
+ /* Check pointers back are correct. */
+ if (uhp->uh_next.ptr != exp_uh_next) {
+ EMSG("uh_next wrong");
+ smsg((char_u *)"expected: 0x%x, actual: 0x%x",
+ exp_uh_next, uhp->uh_next.ptr);
+ }
+ if (uhp->uh_alt_prev.ptr != exp_uh_alt_prev) {
+ EMSG("uh_alt_prev wrong");
+ smsg((char_u *)"expected: 0x%x, actual: 0x%x",
+ exp_uh_alt_prev, uhp->uh_alt_prev.ptr);
+ }
+
+ /* Check the undo tree at this header. */
+ for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) {
+ if (uep->ue_magic != UE_MAGIC) {
+ EMSG("ue_magic wrong (may be using freed memory)");
+ break;
+ }
+ }
+
+ /* Check the next alt tree. */
+ u_check_tree(uhp->uh_alt_next.ptr, uhp->uh_next.ptr, uhp);
+
+ /* Check the next header in this branch. */
+ u_check_tree(uhp->uh_prev.ptr, uhp, NULL);
+ }
+}
+
+static void u_check(int newhead_may_be_NULL) {
+ seen_b_u_newhead = 0;
+ seen_b_u_curhead = 0;
+ header_count = 0;
+
+ u_check_tree(curbuf->b_u_oldhead, NULL, NULL);
+
+ if (seen_b_u_newhead == 0 && curbuf->b_u_oldhead != NULL
+ && !(newhead_may_be_NULL && curbuf->b_u_newhead == NULL))
+ EMSGN("b_u_newhead invalid: 0x%x", curbuf->b_u_newhead);
+ if (curbuf->b_u_curhead != NULL && seen_b_u_curhead == 0)
+ EMSGN("b_u_curhead invalid: 0x%x", curbuf->b_u_curhead);
+ if (header_count != curbuf->b_u_numhead) {
+ EMSG("b_u_numhead invalid");
+ smsg((char_u *)"expected: %" PRId64 ", actual: %" PRId64,
+ (int64_t)header_count, (int64_t)curbuf->b_u_numhead);
+ }
+}
+
+#endif
+
+/*
+ * Save the current line for both the "u" and "U" command.
+ * Careful: may trigger autocommands that reload the buffer.
+ * Returns OK or FAIL.
+ */
+int u_save_cursor(void)
+{
+ return u_save((linenr_T)(curwin->w_cursor.lnum - 1),
+ (linenr_T)(curwin->w_cursor.lnum + 1));
+}
+
+/*
+ * Save the lines between "top" and "bot" for both the "u" and "U" command.
+ * "top" may be 0 and bot may be curbuf->b_ml.ml_line_count + 1.
+ * Careful: may trigger autocommands that reload the buffer.
+ * Returns FAIL when lines could not be saved, OK otherwise.
+ */
+int u_save(linenr_T top, linenr_T bot)
+{
+ if (undo_off)
+ return OK;
+
+ if (top > curbuf->b_ml.ml_line_count
+ || top >= bot
+ || bot > curbuf->b_ml.ml_line_count + 1)
+ return FALSE; /* rely on caller to do error messages */
+
+ if (top + 2 == bot)
+ u_saveline((linenr_T)(top + 1));
+
+ return u_savecommon(top, bot, (linenr_T)0, FALSE);
+}
+
+/*
+ * Save the line "lnum" (used by ":s" and "~" command).
+ * The line is replaced, so the new bottom line is lnum + 1.
+ * Careful: may trigger autocommands that reload the buffer.
+ * Returns FAIL when lines could not be saved, OK otherwise.
+ */
+int u_savesub(linenr_T lnum)
+{
+ if (undo_off)
+ return OK;
+
+ return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE);
+}
+
+/*
+ * A new line is inserted before line "lnum" (used by :s command).
+ * The line is inserted, so the new bottom line is lnum + 1.
+ * Careful: may trigger autocommands that reload the buffer.
+ * Returns FAIL when lines could not be saved, OK otherwise.
+ */
+int u_inssub(linenr_T lnum)
+{
+ if (undo_off)
+ return OK;
+
+ return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE);
+}
+
+/*
+ * Save the lines "lnum" - "lnum" + nlines (used by delete command).
+ * The lines are deleted, so the new bottom line is lnum, unless the buffer
+ * becomes empty.
+ * Careful: may trigger autocommands that reload the buffer.
+ * Returns FAIL when lines could not be saved, OK otherwise.
+ */
+int u_savedel(linenr_T lnum, long nlines)
+{
+ if (undo_off)
+ return OK;
+
+ return u_savecommon(lnum - 1, lnum + nlines,
+ nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE);
+}
+
+/*
+ * Return TRUE when undo is allowed. Otherwise give an error message and
+ * return FALSE.
+ */
+int undo_allowed(void)
+{
+ /* Don't allow changes when 'modifiable' is off. */
+ if (!curbuf->b_p_ma) {
+ EMSG(_(e_modifiable));
+ return FALSE;
+ }
+
+#ifdef HAVE_SANDBOX
+ /* In the sandbox it's not allowed to change the text. */
+ if (sandbox != 0) {
+ EMSG(_(e_sandbox));
+ return FALSE;
+ }
+#endif
+
+ /* Don't allow changes in the buffer while editing the cmdline. The
+ * caller of getcmdline() may get confused. */
+ if (textlock != 0) {
+ EMSG(_(e_secure));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*
+ * Get the undolevle value for the current buffer.
+ */
+static long get_undolevel(void)
+{
+ if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL)
+ return p_ul;
+ return curbuf->b_p_ul;
+}
+
+/*
+ * Common code for various ways to save text before a change.
+ * "top" is the line above the first changed line.
+ * "bot" is the line below the last changed line.
+ * "newbot" is the new bottom line. Use zero when not known.
+ * "reload" is TRUE when saving for a buffer reload.
+ * Careful: may trigger autocommands that reload the buffer.
+ * Returns FAIL when lines could not be saved, OK otherwise.
+ */
+int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload)
+{
+ linenr_T lnum;
+ long i;
+ u_header_T *uhp;
+ u_header_T *old_curhead;
+ u_entry_T *uep;
+ u_entry_T *prev_uep;
+ long size;
+
+ if (!reload) {
+ /* When making changes is not allowed return FAIL. It's a crude way
+ * to make all change commands fail. */
+ if (!undo_allowed())
+ return FAIL;
+
+
+ /*
+ * Saving text for undo means we are going to make a change. Give a
+ * warning for a read-only file before making the change, so that the
+ * FileChangedRO event can replace the buffer with a read-write version
+ * (e.g., obtained from a source control system).
+ */
+ change_warning(0);
+ if (bot > curbuf->b_ml.ml_line_count + 1) {
+ /* This happens when the FileChangedRO autocommand changes the
+ * file in a way it becomes shorter. */
+ EMSG(_("E881: Line count changed unexpectedly"));
+ return FAIL;
+ }
+ }
+
+#ifdef U_DEBUG
+ u_check(FALSE);
+#endif
+
+ size = bot - top - 1;
+
+ /*
+ * If curbuf->b_u_synced == TRUE make a new header.
+ */
+ if (curbuf->b_u_synced) {
+ /* Need to create new entry in b_changelist. */
+ curbuf->b_new_change = TRUE;
+
+ if (get_undolevel() >= 0) {
+ /*
+ * Make a new header entry. Do this first so that we don't mess
+ * up the undo info when out of memory.
+ */
+ uhp = xmalloc(sizeof(u_header_T));
+#ifdef U_DEBUG
+ uhp->uh_magic = UH_MAGIC;
+#endif
+ } else
+ uhp = NULL;
+
+ /*
+ * If we undid more than we redid, move the entry lists before and
+ * including curbuf->b_u_curhead to an alternate branch.
+ */
+ old_curhead = curbuf->b_u_curhead;
+ if (old_curhead != NULL) {
+ curbuf->b_u_newhead = old_curhead->uh_next.ptr;
+ curbuf->b_u_curhead = NULL;
+ }
+
+ /*
+ * free headers to keep the size right
+ */
+ while (curbuf->b_u_numhead > get_undolevel()
+ && curbuf->b_u_oldhead != NULL) {
+ u_header_T *uhfree = curbuf->b_u_oldhead;
+
+ if (uhfree == old_curhead)
+ /* Can't reconnect the branch, delete all of it. */
+ u_freebranch(curbuf, uhfree, &old_curhead);
+ else if (uhfree->uh_alt_next.ptr == NULL)
+ /* There is no branch, only free one header. */
+ u_freeheader(curbuf, uhfree, &old_curhead);
+ else {
+ /* Free the oldest alternate branch as a whole. */
+ while (uhfree->uh_alt_next.ptr != NULL)
+ uhfree = uhfree->uh_alt_next.ptr;
+ u_freebranch(curbuf, uhfree, &old_curhead);
+ }
+#ifdef U_DEBUG
+ u_check(TRUE);
+#endif
+ }
+
+ if (uhp == NULL) { /* no undo at all */
+ if (old_curhead != NULL)
+ u_freebranch(curbuf, old_curhead, NULL);
+ curbuf->b_u_synced = FALSE;
+ return OK;
+ }
+
+ uhp->uh_prev.ptr = NULL;
+ uhp->uh_next.ptr = curbuf->b_u_newhead;
+ uhp->uh_alt_next.ptr = old_curhead;
+ if (old_curhead != NULL) {
+ uhp->uh_alt_prev.ptr = old_curhead->uh_alt_prev.ptr;
+ if (uhp->uh_alt_prev.ptr != NULL)
+ uhp->uh_alt_prev.ptr->uh_alt_next.ptr = uhp;
+ old_curhead->uh_alt_prev.ptr = uhp;
+ if (curbuf->b_u_oldhead == old_curhead)
+ curbuf->b_u_oldhead = uhp;
+ } else
+ uhp->uh_alt_prev.ptr = NULL;
+ if (curbuf->b_u_newhead != NULL)
+ curbuf->b_u_newhead->uh_prev.ptr = uhp;
+
+ uhp->uh_seq = ++curbuf->b_u_seq_last;
+ curbuf->b_u_seq_cur = uhp->uh_seq;
+ uhp->uh_time = time(NULL);
+ uhp->uh_save_nr = 0;
+ curbuf->b_u_time_cur = uhp->uh_time + 1;
+
+ uhp->uh_walk = 0;
+ uhp->uh_entry = NULL;
+ uhp->uh_getbot_entry = NULL;
+ uhp->uh_cursor = curwin->w_cursor; /* save cursor pos. for undo */
+ if (virtual_active() && curwin->w_cursor.coladd > 0)
+ uhp->uh_cursor_vcol = getviscol();
+ else
+ uhp->uh_cursor_vcol = -1;
+
+ /* save changed and buffer empty flag for undo */
+ uhp->uh_flags = (curbuf->b_changed ? UH_CHANGED : 0) +
+ ((curbuf->b_ml.ml_flags & ML_EMPTY) ? UH_EMPTYBUF : 0);
+
+ /* save named marks and Visual marks for undo */
+ memmove(uhp->uh_namedm, curbuf->b_namedm, sizeof(pos_T) * NMARKS);
+ uhp->uh_visual = curbuf->b_visual;
+
+ curbuf->b_u_newhead = uhp;
+ if (curbuf->b_u_oldhead == NULL)
+ curbuf->b_u_oldhead = uhp;
+ ++curbuf->b_u_numhead;
+ } else {
+ if (get_undolevel() < 0) /* no undo at all */
+ return OK;
+
+ /*
+ * When saving a single line, and it has been saved just before, it
+ * doesn't make sense saving it again. Saves a lot of memory when
+ * making lots of changes inside the same line.
+ * This is only possible if the previous change didn't increase or
+ * decrease the number of lines.
+ * Check the ten last changes. More doesn't make sense and takes too
+ * long.
+ */
+ if (size == 1) {
+ uep = u_get_headentry();
+ prev_uep = NULL;
+ for (i = 0; i < 10; ++i) {
+ if (uep == NULL)
+ break;
+
+ /* If lines have been inserted/deleted we give up.
+ * Also when the line was included in a multi-line save. */
+ if ((curbuf->b_u_newhead->uh_getbot_entry != uep
+ ? (uep->ue_top + uep->ue_size + 1
+ != (uep->ue_bot == 0
+ ? curbuf->b_ml.ml_line_count + 1
+ : uep->ue_bot))
+ : uep->ue_lcount != curbuf->b_ml.ml_line_count)
+ || (uep->ue_size > 1
+ && top >= uep->ue_top
+ && top + 2 <= uep->ue_top + uep->ue_size + 1))
+ break;
+
+ /* If it's the same line we can skip saving it again. */
+ if (uep->ue_size == 1 && uep->ue_top == top) {
+ if (i > 0) {
+ /* It's not the last entry: get ue_bot for the last
+ * entry now. Following deleted/inserted lines go to
+ * the re-used entry. */
+ u_getbot();
+ curbuf->b_u_synced = FALSE;
+
+ /* Move the found entry to become the last entry. The
+ * order of undo/redo doesn't matter for the entries
+ * we move it over, since they don't change the line
+ * count and don't include this line. It does matter
+ * for the found entry if the line count is changed by
+ * the executed command. */
+ prev_uep->ue_next = uep->ue_next;
+ uep->ue_next = curbuf->b_u_newhead->uh_entry;
+ curbuf->b_u_newhead->uh_entry = uep;
+ }
+
+ /* The executed command may change the line count. */
+ if (newbot != 0)
+ uep->ue_bot = newbot;
+ else if (bot > curbuf->b_ml.ml_line_count)
+ uep->ue_bot = 0;
+ else {
+ uep->ue_lcount = curbuf->b_ml.ml_line_count;
+ curbuf->b_u_newhead->uh_getbot_entry = uep;
+ }
+ return OK;
+ }
+ prev_uep = uep;
+ uep = uep->ue_next;
+ }
+ }
+
+ /* find line number for ue_bot for previous u_save() */
+ u_getbot();
+ }
+
+ /*
+ * add lines in front of entry list
+ */
+ uep = xmalloc(sizeof(u_entry_T));
+ memset(uep, 0, sizeof(u_entry_T));
+#ifdef U_DEBUG
+ uep->ue_magic = UE_MAGIC;
+#endif
+
+ uep->ue_size = size;
+ uep->ue_top = top;
+ if (newbot != 0)
+ uep->ue_bot = newbot;
+ /*
+ * Use 0 for ue_bot if bot is below last line.
+ * Otherwise we have to compute ue_bot later.
+ */
+ else if (bot > curbuf->b_ml.ml_line_count)
+ uep->ue_bot = 0;
+ else {
+ uep->ue_lcount = curbuf->b_ml.ml_line_count;
+ curbuf->b_u_newhead->uh_getbot_entry = uep;
+ }
+
+ if (size > 0) {
+ uep->ue_array = xmalloc(sizeof(char_u *) * size);
+ for (i = 0, lnum = top + 1; i < size; ++i) {
+ fast_breakcheck();
+ if (got_int) {
+ u_freeentry(uep, i);
+ return FAIL;
+ }
+ if ((uep->ue_array[i] = u_save_line(lnum++)) == NULL) {
+ u_freeentry(uep, i);
+ goto nomem;
+ }
+ }
+ } else
+ uep->ue_array = NULL;
+ uep->ue_next = curbuf->b_u_newhead->uh_entry;
+ curbuf->b_u_newhead->uh_entry = uep;
+ curbuf->b_u_synced = FALSE;
+ undo_undoes = FALSE;
+
+#ifdef U_DEBUG
+ u_check(FALSE);
+#endif
+ return OK;
+
+nomem:
+ msg_silent = 0; /* must display the prompt */
+ if (ask_yesno((char_u *)_("No undo possible; continue anyway"), TRUE)
+ == 'y') {
+ undo_off = TRUE; /* will be reset when character typed */
+ return OK;
+ }
+ do_outofmem_msg((long_u)0);
+ return FAIL;
+}
+
+
+# define UF_START_MAGIC "Vim\237UnDo\345" /* magic at start of undofile */
+# 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 */
+# define UF_VERSION_CRYPT 0x8002 /* idem, encrypted */
+
+/* extra fields for header */
+# define UF_LAST_SAVE_NR 1
+
+/* extra fields for uhp */
+# define UHP_SAVE_NR 1
+
+static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
+
+/*
+ * Compute the hash for the current buffer text into hash[UNDO_HASH_SIZE].
+ */
+void u_compute_hash(char_u *hash)
+{
+ context_sha256_T ctx;
+ linenr_T lnum;
+ char_u *p;
+
+ sha256_start(&ctx);
+ for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) {
+ p = ml_get(lnum);
+ sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1));
+ }
+ sha256_finish(&ctx, hash);
+}
+
+/*
+ * Return an allocated string of the full path of the target undofile.
+ * When "reading" is TRUE find the file to read, go over all directories in
+ * 'undodir'.
+ * When "reading" is FALSE use the first name where the directory exists.
+ * Returns NULL when there is no place to write or no file to read.
+ */
+char_u *u_get_undo_file_name(char_u *buf_ffname, int reading)
+{
+ char_u *dirp;
+ char_u dir_name[IOSIZE + 1];
+ char_u *munged_name = NULL;
+ char_u *undo_file_name = NULL;
+ int dir_len;
+ char_u *p;
+ char_u *ffname = buf_ffname;
+#ifdef HAVE_READLINK
+ char_u fname_buf[MAXPATHL];
+#endif
+
+ if (ffname == NULL)
+ return NULL;
+
+#ifdef HAVE_READLINK
+ /* Expand symlink in the file name, so that we put the undo file with the
+ * actual file instead of with the symlink. */
+ if (resolve_symlink(ffname, fname_buf) == OK)
+ ffname = fname_buf;
+#endif
+
+ /* Loop over 'undodir'. When reading find the first file that exists.
+ * When not reading use the first directory that exists or ".". */
+ dirp = p_udir;
+ while (*dirp != NUL) {
+ dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ",");
+ if (dir_len == 1 && dir_name[0] == '.') {
+ /* Use same directory as the ffname,
+ * "dir/name" -> "dir/.name.un~" */
+ undo_file_name = vim_strnsave(ffname, (int)(STRLEN(ffname) + 5));
+ if (undo_file_name == NULL)
+ break;
+ p = path_tail(undo_file_name);
+ memmove(p + 1, p, STRLEN(p) + 1);
+ *p = '.';
+ STRCAT(p, ".un~");
+ } else {
+ dir_name[dir_len] = NUL;
+ if (os_isdir(dir_name)) {
+ if (munged_name == NULL) {
+ munged_name = vim_strsave(ffname);
+ if (munged_name == NULL)
+ return NULL;
+ for (p = munged_name; *p != NUL; mb_ptr_adv(p))
+ if (vim_ispathsep(*p))
+ *p = '%';
+ }
+ undo_file_name = concat_fnames(dir_name, munged_name, TRUE);
+ }
+ }
+
+ // When reading check if the file exists.
+ if (undo_file_name != NULL &&
+ (!reading || os_file_exists(undo_file_name))) {
+ break;
+ }
+ free(undo_file_name);
+ undo_file_name = NULL;
+ }
+
+ free(munged_name);
+ return undo_file_name;
+}
+
+static void corruption_error(char *mesg, char_u *file_name)
+{
+ EMSG3(_("E825: Corrupted undo file (%s): %s"), mesg, file_name);
+}
+
+static void u_free_uhp(u_header_T *uhp)
+{
+ u_entry_T *nuep;
+ u_entry_T *uep;
+
+ uep = uhp->uh_entry;
+ while (uep != NULL) {
+ nuep = uep->ue_next;
+ u_freeentry(uep, uep->ue_size);
+ uep = nuep;
+ }
+ free(uhp);
+}
+
+/*
+ * Like fwrite() but crypt the bytes when 'key' is set.
+ * Returns 1 if successful.
+ */
+static size_t fwrite_crypt(buf_T *buf, char_u *ptr, size_t len, FILE *fp)
+{
+ char_u *copy;
+ char_u small_buf[100];
+ size_t i;
+
+ if (*buf->b_p_key == NUL)
+ return fwrite(ptr, len, (size_t)1, fp);
+ if (len < 100)
+ copy = small_buf; /* no malloc()/free() for short strings */
+ else {
+ copy = xmalloc(len);
+ }
+ crypt_encode(ptr, len, copy);
+ i = fwrite(copy, len, (size_t)1, fp);
+ if (copy != small_buf)
+ free(copy);
+ return i;
+}
+
+/*
+ * Read a string of length "len" from "fd".
+ * When 'key' is set decrypt the bytes.
+ */
+static char_u *read_string_decrypt(buf_T *buf, FILE *fd, int len)
+{
+ char_u *ptr;
+
+ ptr = read_string(fd, len);
+ if (ptr != NULL && *buf->b_p_key != NUL)
+ crypt_decode(ptr, len);
+ return ptr;
+}
+
+static int serialize_header(FILE *fp, buf_T *buf, char_u *hash)
+{
+ int len;
+
+ /* Start writing, first the magic marker and undo info version. */
+ if (fwrite(UF_START_MAGIC, (size_t)UF_START_MAGIC_LEN, (size_t)1, fp) != 1)
+ return FAIL;
+
+ /* If the buffer is encrypted then all text bytes following will be
+ * encrypted. Numbers and other info is not crypted. */
+ if (*buf->b_p_key != NUL) {
+ char_u *header;
+ int header_len;
+
+ put_bytes(fp, (long_u)UF_VERSION_CRYPT, 2);
+ header = prepare_crypt_write(buf, &header_len);
+ if (header == NULL)
+ return FAIL;
+ len = (int)fwrite(header, (size_t)header_len, (size_t)1, fp);
+ free(header);
+ if (len != 1) {
+ crypt_pop_state();
+ return FAIL;
+ }
+ } else
+ put_bytes(fp, (long_u)UF_VERSION, 2);
+
+
+ /* Write a hash of the buffer text, so that we can verify it is still the
+ * same when reading the buffer text. */
+ if (fwrite(hash, (size_t)UNDO_HASH_SIZE, (size_t)1, fp) != 1)
+ return FAIL;
+
+ /* buffer-specific data */
+ put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4);
+ len = buf->b_u_line_ptr != NULL ? (int)STRLEN(buf->b_u_line_ptr) : 0;
+ put_bytes(fp, (long_u)len, 4);
+ if (len > 0 && fwrite_crypt(buf, buf->b_u_line_ptr, (size_t)len, fp) != 1)
+ return FAIL;
+ put_bytes(fp, (long_u)buf->b_u_line_lnum, 4);
+ put_bytes(fp, (long_u)buf->b_u_line_colnr, 4);
+
+ /* Undo structures header data */
+ put_header_ptr(fp, buf->b_u_oldhead);
+ put_header_ptr(fp, buf->b_u_newhead);
+ put_header_ptr(fp, buf->b_u_curhead);
+
+ put_bytes(fp, (long_u)buf->b_u_numhead, 4);
+ put_bytes(fp, (long_u)buf->b_u_seq_last, 4);
+ put_bytes(fp, (long_u)buf->b_u_seq_cur, 4);
+ put_time(fp, buf->b_u_time_cur);
+
+ /* Optional fields. */
+ putc(4, fp);
+ putc(UF_LAST_SAVE_NR, fp);
+ put_bytes(fp, (long_u)buf->b_u_save_nr_last, 4);
+
+ putc(0, fp); /* end marker */
+
+ return OK;
+}
+
+static int serialize_uhp(FILE *fp, buf_T *buf, u_header_T *uhp)
+{
+ int i;
+ u_entry_T *uep;
+
+ if (put_bytes(fp, (long_u)UF_HEADER_MAGIC, 2) == FAIL)
+ return FAIL;
+
+ put_header_ptr(fp, uhp->uh_next.ptr);
+ put_header_ptr(fp, uhp->uh_prev.ptr);
+ put_header_ptr(fp, uhp->uh_alt_next.ptr);
+ put_header_ptr(fp, uhp->uh_alt_prev.ptr);
+ put_bytes(fp, uhp->uh_seq, 4);
+ serialize_pos(uhp->uh_cursor, fp);
+ put_bytes(fp, (long_u)uhp->uh_cursor_vcol, 4);
+ put_bytes(fp, (long_u)uhp->uh_flags, 2);
+ /* Assume NMARKS will stay the same. */
+ for (i = 0; i < NMARKS; ++i)
+ serialize_pos(uhp->uh_namedm[i], fp);
+ serialize_visualinfo(&uhp->uh_visual, fp);
+ put_time(fp, uhp->uh_time);
+
+ /* Optional fields. */
+ putc(4, fp);
+ putc(UHP_SAVE_NR, fp);
+ put_bytes(fp, (long_u)uhp->uh_save_nr, 4);
+
+ putc(0, fp); /* end marker */
+
+ /* Write all the entries. */
+ for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) {
+ put_bytes(fp, (long_u)UF_ENTRY_MAGIC, 2);
+ if (serialize_uep(fp, buf, uep) == FAIL)
+ return FAIL;
+ }
+ put_bytes(fp, (long_u)UF_ENTRY_END_MAGIC, 2);
+ return OK;
+}
+
+static u_header_T *unserialize_uhp(FILE *fp, char_u *file_name)
+{
+ u_header_T *uhp;
+ int i;
+ u_entry_T *uep, *last_uep;
+ int c;
+ int error;
+
+ uhp = xmalloc(sizeof(u_header_T));
+ memset(uhp, 0, sizeof(u_header_T));
+#ifdef U_DEBUG
+ uhp->uh_magic = UH_MAGIC;
+#endif
+ uhp->uh_next.seq = get4c(fp);
+ uhp->uh_prev.seq = get4c(fp);
+ uhp->uh_alt_next.seq = get4c(fp);
+ uhp->uh_alt_prev.seq = get4c(fp);
+ uhp->uh_seq = get4c(fp);
+ if (uhp->uh_seq <= 0) {
+ corruption_error("uh_seq", file_name);
+ free(uhp);
+ return NULL;
+ }
+ unserialize_pos(&uhp->uh_cursor, fp);
+ uhp->uh_cursor_vcol = get4c(fp);
+ uhp->uh_flags = get2c(fp);
+ for (i = 0; i < NMARKS; ++i)
+ unserialize_pos(&uhp->uh_namedm[i], fp);
+ unserialize_visualinfo(&uhp->uh_visual, fp);
+ uhp->uh_time = get8ctime(fp);
+
+ /* Optional fields. */
+ for (;; ) {
+ int len = getc(fp);
+ int what;
+
+ if (len == 0)
+ break;
+ what = getc(fp);
+ switch (what) {
+ case UHP_SAVE_NR:
+ uhp->uh_save_nr = get4c(fp);
+ break;
+ default:
+ /* field not supported, skip */
+ while (--len >= 0)
+ (void)getc(fp);
+ }
+ }
+
+ /* Unserialize the uep list. */
+ last_uep = NULL;
+ while ((c = get2c(fp)) == UF_ENTRY_MAGIC) {
+ error = FALSE;
+ uep = unserialize_uep(fp, &error, file_name);
+ if (last_uep == NULL)
+ uhp->uh_entry = uep;
+ else
+ last_uep->ue_next = uep;
+ last_uep = uep;
+ if (uep == NULL || error) {
+ u_free_uhp(uhp);
+ return NULL;
+ }
+ }
+ if (c != UF_ENTRY_END_MAGIC) {
+ corruption_error("entry end", file_name);
+ u_free_uhp(uhp);
+ return NULL;
+ }
+
+ return uhp;
+}
+
+/*
+ * Serialize "uep" to "fp".
+ */
+static int serialize_uep(FILE *fp, buf_T *buf, u_entry_T *uep)
+{
+ int i;
+ size_t len;
+
+ put_bytes(fp, (long_u)uep->ue_top, 4);
+ put_bytes(fp, (long_u)uep->ue_bot, 4);
+ put_bytes(fp, (long_u)uep->ue_lcount, 4);
+ put_bytes(fp, (long_u)uep->ue_size, 4);
+ for (i = 0; i < uep->ue_size; ++i) {
+ len = STRLEN(uep->ue_array[i]);
+ if (put_bytes(fp, (long_u)len, 4) == FAIL)
+ return FAIL;
+ if (len > 0 && fwrite_crypt(buf, uep->ue_array[i], len, fp) != 1)
+ return FAIL;
+ }
+ return OK;
+}
+
+static u_entry_T *unserialize_uep(FILE *fp, int *error, char_u *file_name)
+{
+ int i;
+ u_entry_T *uep;
+ char_u **array;
+ char_u *line;
+ int line_len;
+
+ uep = xmalloc(sizeof(u_entry_T));
+ memset(uep, 0, sizeof(u_entry_T));
+#ifdef U_DEBUG
+ uep->ue_magic = UE_MAGIC;
+#endif
+ uep->ue_top = get4c(fp);
+ uep->ue_bot = get4c(fp);
+ uep->ue_lcount = get4c(fp);
+ uep->ue_size = get4c(fp);
+ if (uep->ue_size > 0) {
+ array = xmalloc(sizeof(char_u *) * uep->ue_size);
+ memset(array, 0, sizeof(char_u *) * uep->ue_size);
+ } else
+ array = NULL;
+ uep->ue_array = array;
+
+ for (i = 0; i < uep->ue_size; ++i) {
+ line_len = get4c(fp);
+ if (line_len >= 0)
+ line = read_string_decrypt(curbuf, fp, line_len);
+ else {
+ line = NULL;
+ corruption_error("line length", file_name);
+ }
+ if (line == NULL) {
+ *error = TRUE;
+ return uep;
+ }
+ array[i] = line;
+ }
+ return uep;
+}
+
+/*
+ * Serialize "pos" to "fp".
+ */
+static void serialize_pos(pos_T pos, FILE *fp)
+{
+ put_bytes(fp, (long_u)pos.lnum, 4);
+ put_bytes(fp, (long_u)pos.col, 4);
+ put_bytes(fp, (long_u)pos.coladd, 4);
+}
+
+/*
+ * Unserialize the pos_T at the current position in fp.
+ */
+static void unserialize_pos(pos_T *pos, FILE *fp)
+{
+ pos->lnum = get4c(fp);
+ if (pos->lnum < 0)
+ pos->lnum = 0;
+ pos->col = get4c(fp);
+ if (pos->col < 0)
+ pos->col = 0;
+ pos->coladd = get4c(fp);
+ if (pos->coladd < 0)
+ pos->coladd = 0;
+}
+
+/*
+ * Serialize "info" to "fp".
+ */
+static void serialize_visualinfo(visualinfo_T *info, FILE *fp)
+{
+ serialize_pos(info->vi_start, fp);
+ serialize_pos(info->vi_end, fp);
+ put_bytes(fp, (long_u)info->vi_mode, 4);
+ put_bytes(fp, (long_u)info->vi_curswant, 4);
+}
+
+/*
+ * Unserialize the visualinfo_T at the current position in fp.
+ */
+static void unserialize_visualinfo(visualinfo_T *info, FILE *fp)
+{
+ unserialize_pos(&info->vi_start, fp);
+ unserialize_pos(&info->vi_end, fp);
+ info->vi_mode = get4c(fp);
+ info->vi_curswant = get4c(fp);
+}
+
+/*
+ * Write the pointer to an undo header. Instead of writing the pointer itself
+ * we use the sequence number of the header. This is converted back to
+ * pointers when reading. */
+static void put_header_ptr(FIL