aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/undo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/undo.c')
-rw-r--r--src/nvim/undo.c247
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) &&