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.c366
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)));
}
/*