diff options
Diffstat (limited to 'src/nvim/undo.c')
-rw-r--r-- | src/nvim/undo.c | 366 |
1 files changed, 216 insertions, 150 deletions
diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 2b0ffefa7e..f16b4264d7 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1,40 +1,32 @@ /* - * 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. + * | + * 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), @@ -45,30 +37,30 @@ * 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. + * +---------------+ +---------------+ + * 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. @@ -83,9 +75,9 @@ #include <assert.h> #include <inttypes.h> #include <limits.h> -#include <errno.h> #include <stdbool.h> #include <string.h> +#include <fcntl.h> #include "nvim/vim.h" #include "nvim/ascii.h" @@ -284,32 +276,32 @@ int u_savedel(linenr_T lnum, long 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) +/// Return true when undo is allowed. Otherwise print an error message and +/// return false. +/// +/// @return true if undo is allowed. +bool undo_allowed(void) { /* Don't allow changes when 'modifiable' is off. */ if (!MODIFIABLE(curbuf)) { EMSG(_(e_modifiable)); - return FALSE; + return false; } // In the sandbox it's not allowed to change the text. if (sandbox != 0) { EMSG(_(e_sandbox)); - return FALSE; + return false; } /* 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 false; } - return TRUE; + return true; } /* @@ -638,64 +630,89 @@ void u_compute_hash(char_u *hash) 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) +/// Return an allocated string of the full path of the target undofile. +/// +/// @param[in] buf_ffname Full file name for which undo file location should +/// be found. +/// @param[in] reading If true, find the file to read by traversing all of the +/// directories in &undodir. If false use the first +/// existing directory. If none of the directories in +/// &undodir option exist then last directory in the list +/// will be automatically created. +/// +/// @return [allocated] File name to read from/write to or NULL. +char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) + FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *dirp; - char_u dir_name[IOSIZE + 1]; - char_u *munged_name = NULL; - char_u *undo_file_name = NULL; - char_u *p; - char_u *ffname = buf_ffname; + char *dirp; + char dir_name[MAXPATHL + 1]; + char *munged_name = NULL; + char *undo_file_name = NULL; + const char *ffname = buf_ffname; #ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; + char fname_buf[MAXPATHL]; #endif - if (ffname == NULL) + 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) + // 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((const char_u *)ffname, (char_u *)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; + // Loop over 'undodir'. When reading find the first file that exists. + // When not reading use the first directory that exists or ".". + dirp = (char *) p_udir; while (*dirp != NUL) { - size_t dir_len = copy_option_part(&dirp, dir_name, IOSIZE, ","); + size_t dir_len = copy_option_part((char_u **)&dirp, (char_u *)dir_name, + MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { - /* Use same directory as the ffname, - * "dir/name" -> "dir/.name.un~" */ - undo_file_name = vim_strnsave(ffname, STRLEN(ffname) + 5); - p = path_tail(undo_file_name); - memmove(p + 1, p, STRLEN(p) + 1); - *p = '.'; - STRCAT(p, ".un~"); + // Use same directory as the ffname, + // "dir/name" -> "dir/.name.un~" + const size_t ffname_len = strlen(ffname); + undo_file_name = xmalloc(ffname_len + 6); + memmove(undo_file_name, ffname, ffname_len + 1); + char *const tail = (char *) path_tail((char_u *) undo_file_name); + const size_t tail_len = strlen(tail); + memmove(tail + 1, tail, tail_len + 1); + *tail = '.'; + memmove(tail + tail_len + 1, ".un~", sizeof(".un~")); } else { dir_name[dir_len] = NUL; - if (os_isdir(dir_name)) { + bool has_directory = os_isdir((char_u *)dir_name); + if (!has_directory && *dirp == NUL && !reading) { + // Last directory in the list does not exist, create it. + int ret; + char *failed_dir; + if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) { + EMSG3(_("E926: Unable to create directory \"%s\" for undo file: %s"), + failed_dir, os_strerror(ret)); + xfree(failed_dir); + } else { + has_directory = true; + } + } + if (has_directory) { if (munged_name == NULL) { - munged_name = vim_strsave(ffname); - for (p = munged_name; *p != NUL; mb_ptr_adv(p)) - if (vim_ispathsep(*p)) + munged_name = xstrdup(ffname); + for (char *p = munged_name; *p != NUL; mb_ptr_adv(p)) { + if (vim_ispathsep(*p)) { *p = '%'; + } + } } - undo_file_name = (char_u *)concat_fnames((char *)dir_name, (char *)munged_name, TRUE); + 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))) { + if (undo_file_name != NULL + && (!reading || os_file_exists((char_u *)undo_file_name))) { break; } xfree(undo_file_name); @@ -706,7 +723,13 @@ char_u *u_get_undo_file_name(char_u *buf_ffname, int reading) return undo_file_name; } -static void corruption_error(char *mesg, char_u *file_name) +/// Display an error for corrupted undo file +/// +/// @param[in] mesg Identifier of the corruption kind. +/// @param[in] file_name File in which error occurred. +static void corruption_error(const char *const mesg, + const char *const file_name) + FUNC_ATTR_NONNULL_ALL { EMSG3(_("E825: Corrupted undo file (%s): %s"), mesg, file_name); } @@ -725,7 +748,11 @@ static void u_free_uhp(u_header_T *uhp) xfree(uhp); } -/// Writes the header. +/// Writes the undofile header. +/// +/// @param bi The buffer information +/// @param hash The hash of the buffer contents +// /// @returns false in case of an error. static bool serialize_header(bufinfo_T *bi, char_u *hash) FUNC_ATTR_NONNULL_ALL @@ -779,6 +806,12 @@ static bool serialize_header(bufinfo_T *bi, char_u *hash) return true; } +/// Writes an undo header. +/// +/// @param bi The buffer information +/// @param uhp The undo header to write +// +/// @returns false in case of an error. static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) { if (!undo_write_bytes(bi, (uintmax_t)UF_HEADER_MAGIC, 2)) { @@ -821,7 +854,8 @@ static bool serialize_uhp(bufinfo_T *bi, u_header_T *uhp) return true; } -static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name) +static u_header_T *unserialize_uhp(bufinfo_T *bi, + const char *file_name) { u_header_T *uhp = xmalloc(sizeof(u_header_T)); memset(uhp, 0, sizeof(u_header_T)); @@ -898,6 +932,9 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, char_u *file_name) /// Serializes "uep". /// +/// @param bi The buffer information +/// @param uep The undo entry to write +// /// @returns false in case of an error. static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) { @@ -918,7 +955,8 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) return true; } -static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, char_u *file_name) +static u_entry_T *unserialize_uep(bufinfo_T * bi, bool *error, + const char *file_name) { u_entry_T *uep = xmalloc(sizeof(u_entry_T)); memset(uep, 0, sizeof(u_entry_T)); @@ -1000,19 +1038,20 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) info->vi_curswant = undo_read_4c(bi); } -/* - * Write the undo tree in an undo file. - * When "name" is not NULL, use it as the name of the undo file. - * Otherwise use buf->b_ffname to generate the undo file name. - * "buf" must never be null, buf->b_ffname is used to obtain the original file - * permissions. - * "forceit" is TRUE for ":wundo!", FALSE otherwise. - * "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. - */ -void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) +/// Write the undo tree in an undo file. +/// +/// @param[in] name Name of the undo file or NULL if this function needs to +/// generate the undo file name based on buf->b_ffname. +/// @param[in] forceit True for `:wundo!`, false otherwise. +/// @param[in] buf Buffer for which undo file is written. +/// @param[in] hash Hash value of the buffer text. Must have #UNDO_HASH_SIZE +/// size. +void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, + char_u *const hash) + FUNC_ATTR_NONNULL_ARG(3, 4) { u_header_T *uhp; - char_u *file_name; + char *file_name; int mark; #ifdef U_DEBUG int headers_written = 0; @@ -1024,7 +1063,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) bufinfo_T bi; if (name == NULL) { - file_name = u_get_undo_file_name(buf->b_ffname, FALSE); + file_name = u_get_undo_file_name((char *) buf->b_ffname, false); if (file_name == NULL) { if (p_verbose > 0) { verbose_enter(); @@ -1033,8 +1072,9 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) } return; } - } else - file_name = name; + } else { + file_name = (char *) name; + } /* * Decide about the permission to use for the undo file. If the buffer @@ -1054,10 +1094,10 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) /* If the undo file already exists, verify that it actually is an undo * file, and delete it. */ - if (os_file_exists(file_name)) { + if (os_file_exists((char_u *)file_name)) { if (name == NULL || !forceit) { /* Check we can read it and it's an undo file. */ - fd = os_open((char *)file_name, O_RDONLY, 0); + fd = os_open(file_name, O_RDONLY, 0); if (fd < 0) { if (name != NULL || p_verbose > 0) { if (name == NULL) @@ -1086,7 +1126,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) } } } - os_remove((char *)file_name); + os_remove(file_name); } /* If there is no undo information at all, quit here after deleting any @@ -1097,13 +1137,12 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) goto theend; } - fd = os_open((char *)file_name, - O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); + fd = os_open(file_name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); if (fd < 0) { EMSG2(_(e_not_open), file_name); goto theend; } - (void)os_setperm(file_name, perm); + (void)os_setperm((char_u *)file_name, perm); if (p_verbose > 0) { verbose_enter(); smsg(_("Writing undo file: %s"), file_name); @@ -1125,10 +1164,10 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) FileInfo file_info_new; if (buf->b_ffname != NULL && os_fileinfo((char *)buf->b_ffname, &file_info_old) - && os_fileinfo((char *)file_name, &file_info_new) + && os_fileinfo(file_name, &file_info_new) && file_info_old.stat.st_gid != file_info_new.stat.st_gid && os_fchown(fd, (uv_uid_t)-1, (uv_gid_t)file_info_old.stat.st_gid)) { - os_setperm(file_name, (perm & 0707) | ((perm & 07) << 3)); + os_setperm((char_u *)file_name, (perm & 0707) | ((perm & 07) << 3)); } # ifdef HAVE_SELINUX if (buf->b_ffname != NULL) @@ -1140,7 +1179,7 @@ void u_write_undo(char_u *name, int forceit, buf_T *buf, char_u *hash) if (fp == NULL) { EMSG2(_(e_not_open), file_name); close(fd); - os_remove((char *)file_name); + os_remove(file_name); goto theend; } @@ -1209,7 +1248,7 @@ write_error: /* For systems that support ACL: get the ACL from the original file. */ acl = mch_get_acl(buf->b_ffname); - mch_set_acl(file_name, acl); + mch_set_acl((char_u *)file_name, acl); mch_free_acl(acl); } #endif @@ -1224,15 +1263,15 @@ theend: /// a bit more verbose. /// Otherwise use curbuf->b_ffname to generate the undo file name. /// "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. -void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) +void u_read_undo(char *name, char_u *hash, char_u *orig_name) FUNC_ATTR_NONNULL_ARG(2) { u_header_T **uhp_table = NULL; char_u *line_ptr = NULL; - char_u *file_name; + char *file_name; if (name == NULL) { - file_name = u_get_undo_file_name(curbuf->b_ffname, TRUE); + file_name = u_get_undo_file_name((char *) curbuf->b_ffname, true); if (file_name == NULL) { return; } @@ -1256,7 +1295,7 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) } #endif } else { - file_name = name; + file_name = (char *) name; } if (p_verbose > 0) { @@ -1265,7 +1304,7 @@ void u_read_undo(char_u *name, char_u *hash, char_u *orig_name) verbose_leave(); } - FILE *fp = mch_fopen((char *)file_name, "r"); + FILE *fp = mch_fopen(file_name, "r"); if (fp == NULL) { if (name != NULL || p_verbose > 0) { EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name); @@ -1520,6 +1559,10 @@ theend: /// Writes a sequence of bytes to the undo file. /// +/// @param bi The buffer info +/// @param ptr The byte buffer to write +/// @param len The number of bytes to write +/// /// @returns false in case of an error. static bool undo_write(bufinfo_T *bi, uint8_t *ptr, size_t len) FUNC_ATTR_NONNULL_ARG(1) @@ -1531,6 +1574,10 @@ static bool undo_write(bufinfo_T *bi, uint8_t *ptr, size_t len) /// /// Must match with undo_read_?c() functions. /// +/// @param bi The buffer info +/// @param nr The number to write +/// @param len The number of bytes to use when writing the number. +/// /// @returns false in case of an error. static bool undo_write_bytes(bufinfo_T *bi, uintmax_t nr, size_t len) { @@ -1575,6 +1622,10 @@ static time_t undo_read_time(bufinfo_T *bi) /// Reads "buffer[size]" from the undo file. /// +/// @param bi The buffer info +/// @param buffer Character buffer to read data into +/// @param size The size of the character buffer +/// /// @returns false in case of an error. static bool undo_read(bufinfo_T *bi, uint8_t *buffer, size_t size) FUNC_ATTR_NONNULL_ARG(1) @@ -1731,7 +1782,13 @@ void undo_time(long step, int sec, int file, int absolute) /* "target" is the node below which we want to be. * Init "closest" to a value we can't reach. */ if (absolute) { - target = step; + if (step == 0) { + // target 0 does not exist, got to 1 and above it. + target = 1; + above = true; + } else { + target = step; + } closest = -1; } else { /* When doing computations with time_t subtract starttime, because @@ -2171,12 +2228,17 @@ static void u_undoredo(int undo) /* * restore marks from before undo/redo */ - for (i = 0; i < NMARKS; ++i) + for (i = 0; i < NMARKS; ++i) { if (curhead->uh_namedm[i].mark.lnum != 0) { free_fmark(curbuf->b_namedm[i]); curbuf->b_namedm[i] = curhead->uh_namedm[i]; + } + if (namedm[i].mark.lnum != 0) { curhead->uh_namedm[i] = namedm[i]; + } else { + curhead->uh_namedm[i].mark.lnum = 0; } + } if (curhead->uh_visual.vi_start.lnum != 0) { curbuf->b_visual = curhead->uh_visual; curhead->uh_visual = visualinfo; @@ -2805,23 +2867,27 @@ static char_u *u_save_line(linenr_T lnum) return vim_strsave(ml_get(lnum)); } -/* - * Check if the 'modified' flag is set, or 'ff' has changed (only need to - * check the first character, because it can only be "dos", "unix" or "mac"). - * "nofile" and "scratch" type buffers are considered to always be unchanged. - */ -int bufIsChanged(buf_T *buf) +/// Check if the 'modified' flag is set, or 'ff' has changed (only need to +/// check the first character, because it can only be "dos", "unix" or "mac"). +/// "nofile" and "scratch" type buffers are considered to always be unchanged. +/// +/// @param buf The buffer to check +/// +/// @return true if the buffer has changed +bool bufIsChanged(buf_T *buf) { - return - !bt_dontwrite(buf) && - (buf->b_changed || file_ff_differs(buf, true)); + return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true)); } -int curbufIsChanged(void) +/// Check if the 'modified' flag is set, or 'ff' has changed (only need to +/// check the first character, because it can only be "dos", "unix" or "mac"). +/// "nofile" and "scratch" type buffers are considered to always be unchanged. +/// +/// @return true if the current buffer has changed +bool curbufIsChanged(void) { - return - !bt_dontwrite(curbuf) && - (curbuf->b_changed || file_ff_differs(curbuf, true)); + return (!bt_dontwrite(curbuf) + && (curbuf->b_changed || file_ff_differs(curbuf, true))); } /* |