diff options
Diffstat (limited to 'src/nvim/undo.c')
| -rw-r--r-- | src/nvim/undo.c | 247 |
1 files changed, 152 insertions, 95 deletions
diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 2b0ffefa7e..69ac18ad54 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -1,12 +1,4 @@ /* - * 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): @@ -83,7 +75,6 @@ #include <assert.h> #include <inttypes.h> #include <limits.h> -#include <errno.h> #include <stdbool.h> #include <string.h> @@ -284,32 +275,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 +629,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 +722,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 +747,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 +805,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 +853,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 +931,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 +954,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 +1037,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 +1062,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 +1071,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 +1093,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 +1125,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 +1136,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 +1163,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 +1178,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 +1247,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 +1262,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 +1294,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 +1303,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 +1558,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 +1573,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 +1621,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) @@ -2805,19 +2855,26 @@ 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)); } -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) && |