diff options
| author | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-12 02:25:17 +0200 |
|---|---|---|
| committer | Eliseo Martínez <eliseomarmol@gmail.com> | 2014-05-15 20:46:01 +0200 |
| commit | da51dc9cf202772f60bd2da975dbef257bd9237c (patch) | |
| tree | 5c16b93238a153f55634e9323077f30c8133970c /src/nvim/undo.c | |
| parent | ffe61e5ba1721340ca51d56bae3ddaca415fb5bc (diff) | |
| download | rneovim-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.c | 2880 |
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 |