From 0bcc2bf1265ba0a88cbcdb9cfc4c0b98bbe96f7a Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 29 Oct 2015 00:28:44 +0300 Subject: [PATCH 1/6] option: Add current directory to &backupdir option Fixes #3496 --- runtime/doc/options.txt | 2 +- src/nvim/option.c | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 3471106afd..7829add2e5 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -901,7 +901,7 @@ A jump table for the options with a short description can be found at |Q_op|. again not rename the file. *'backupdir'* *'bdir'* -'backupdir' 'bdir' string (default "$XDG_DATA_HOME/nvim/backup") +'backupdir' 'bdir' string (default ".,$XDG_DATA_HOME/nvim/backup") global List of directories for the backup file, separated with commas. - The backup file will be created in the first directory in the list diff --git a/src/nvim/option.c b/src/nvim/option.c index f30d10d0bb..cc4df28837 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -685,9 +685,13 @@ void set_init_1(void) #endif false); + char *backupdir = stdpaths_user_data_subpath("backup", 0); + const size_t backupdir_len = strlen(backupdir); + backupdir = xrealloc(backupdir, backupdir_len + 3); + memmove(backupdir + 2, backupdir, backupdir_len + 1); + memmove(backupdir, ".,", 2); set_string_default("viewdir", stdpaths_user_data_subpath("view", 0), true); - set_string_default("backupdir", stdpaths_user_data_subpath("backup", 0), - true); + set_string_default("backupdir", backupdir, true); set_string_default("directory", stdpaths_user_data_subpath("swap", 2), true); set_string_default("undodir", stdpaths_user_data_subpath("undo", 0), true); // Set default for &runtimepath. All necessary expansions are performed in From baf032834aa82f3e9b69f59f144c78ad5a37f5d5 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 29 Oct 2015 00:29:28 +0300 Subject: [PATCH 2/6] stdpaths: Use NULL in place of empty strings --- src/nvim/os/stdpaths.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index ce374828f9..ab59dd9a71 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -25,7 +25,7 @@ static const char *const xdg_defaults[] = { [kXDGConfigHome] = "$LOCALAPPDATA\\nvim\\config", [kXDGDataHome] = "$LOCALAPPDATA\\nvim\\data", [kXDGCacheHome] = "$LOCALAPPDATA\\nvim\\cache", - [kXDGRuntimeDir] = "", + [kXDGRuntimeDir] = NULL, [kXDGConfigDirs] = NULL, [kXDGDataDirs] = NULL, #else @@ -33,7 +33,7 @@ static const char *const xdg_defaults[] = { [kXDGConfigHome] = "~/.config", [kXDGDataHome] = "~/.local/share", [kXDGCacheHome] = "~/.cache", - [kXDGRuntimeDir] = "", + [kXDGRuntimeDir] = NULL, [kXDGConfigDirs] = "/etc/xdg/", [kXDGDataDirs] = "/usr/local/share/:/usr/share/", #endif From 1af15494c25d1cba9e4da058fc3b958d27d3f890 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 29 Oct 2015 00:31:05 +0300 Subject: [PATCH 3/6] stdpaths: Document that stdpaths_*_subpath is not returning NULL --- src/nvim/os/stdpaths.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index ab59dd9a71..c9631a434c 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -82,7 +82,7 @@ static char *get_xdg_home(const XDGVarType idx) /// /// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}` char *stdpaths_user_conf_subpath(const char *fname) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { return concat_fnames_realloc(get_xdg_home(kXDGConfigHome), fname, true); } @@ -95,7 +95,7 @@ char *stdpaths_user_conf_subpath(const char *fname) /// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}` char *stdpaths_user_data_subpath(const char *fname, const size_t trailing_pathseps) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { char *ret = concat_fnames_realloc(get_xdg_home(kXDGDataHome), fname, true); if (trailing_pathseps) { From d99941777dbaca1719223961174df5e01b1ac444 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 29 Oct 2015 01:14:01 +0300 Subject: [PATCH 4/6] undo: Do some refactoring Specifically refactor u_get_undo_file_name which will be modified to automatically create undo directory and replace `char_u` with `char` in some of the related functions. --- src/nvim/eval.c | 7 +- src/nvim/ex_docmd.c | 4 +- src/nvim/memline.c | 2 +- src/nvim/undo.c | 157 +++++++++++++++++++++++++------------------- 4 files changed, 95 insertions(+), 75 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a1ac713a75..7685e422fc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -16678,10 +16678,11 @@ static void f_undofile(typval_T *argvars, typval_T *rettv) /* If there is no file name there will be no undo file. */ rettv->vval.v_string = NULL; } else { - char_u *ffname = (char_u *)FullName_save((char *)fname, FALSE); + char *ffname = FullName_save((char *)fname, false); - if (ffname != NULL) - rettv->vval.v_string = u_get_undo_file_name(ffname, FALSE); + if (ffname != NULL) { + rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); + } xfree(ffname); } } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e160281145..70cf5fd029 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7193,7 +7193,7 @@ static void ex_wundo(exarg_T *eap) char_u hash[UNDO_HASH_SIZE]; u_compute_hash(hash); - u_write_undo(eap->arg, eap->forceit, curbuf, hash); + u_write_undo((char *) eap->arg, eap->forceit, curbuf, hash); } static void ex_rundo(exarg_T *eap) @@ -7201,7 +7201,7 @@ static void ex_rundo(exarg_T *eap) char_u hash[UNDO_HASH_SIZE]; u_compute_hash(hash); - u_read_undo(eap->arg, hash, NULL); + u_read_undo((char *) eap->arg, hash, NULL); } /* diff --git a/src/nvim/memline.c b/src/nvim/memline.c index dbb24e67a1..85f545f960 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -2990,7 +2990,7 @@ static void ml_lineadd(buf_T *buf, int count) * If it worked returns OK and the resolved link in "buf[MAXPATHL]". * Otherwise returns FAIL. */ -int resolve_symlink(char_u *fname, char_u *buf) +int resolve_symlink(const char_u *fname, char_u *buf) { char_u tmp[MAXPATHL]; int ret; diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 2b0ffefa7e..90f02645da 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -638,64 +638,74 @@ 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. +/// +/// @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_NONNULL_ALL 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)) { + if (os_isdir((char_u *)dir_name)) { 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 +716,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); } @@ -821,7 +837,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)); @@ -918,7 +935,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 +1018,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 +1043,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 +1052,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 +1074,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 +1106,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 +1117,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 +1144,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 +1159,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 +1228,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 +1243,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 +1275,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 +1284,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); From ffdf9399ba0a3f1b90bde6f4a3c489a879a01358 Mon Sep 17 00:00:00 2001 From: ZyX Date: Thu, 29 Oct 2015 01:24:04 +0300 Subject: [PATCH 5/6] undo: Automatically create undo directory if needed --- runtime/doc/options.txt | 8 +++++--- src/nvim/undo.c | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 7829add2e5..73a433fd42 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6695,7 +6695,7 @@ A jump table for the options with a short description can be found at |Q_op|. global Alias for 'term', see above. - *'undodir'* *'udir'* + *'undodir'* *'udir'* *E926* 'undodir' 'udir' string (default ".") global {only when compiled with the |+persistent_undo| feature} @@ -6705,8 +6705,10 @@ A jump table for the options with a short description can be found at |Q_op|. "file.txt" is ".file.txt.un~". For other directories the file name is the full path of the edited file, with path separators replaced with "%". - When writing: The first directory that exists is used. "." always - works, no directories after "." will be used for writing. + When writing: The first directory that exists is used. "." always + works, no directories after "." will be used for writing. If none of + the directories exist Neovim will attempt to create last directory in + the list. When reading all entries are tried to find an undo file. The first undo file that exists is used. When it cannot be read an error is given, no further entry is used. diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 90f02645da..d8158bf7cd 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -644,7 +644,9 @@ void u_compute_hash(char_u *hash) /// 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. +/// 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) @@ -690,7 +692,20 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) memmove(tail + tail_len + 1, ".un~", sizeof(".un~")); } else { dir_name[dir_len] = NUL; - if (os_isdir((char_u *)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 = xstrdup(ffname); for (char *p = munged_name; *p != NUL; mb_ptr_adv(p)) { From 10080366b8975e827b64fda49a9686117c8c8f44 Mon Sep 17 00:00:00 2001 From: ZyX Date: Fri, 30 Oct 2015 17:42:54 +0300 Subject: [PATCH 6/6] documentation: Fix &undodir default --- runtime/doc/options.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 73a433fd42..baafe8cbea 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -6696,7 +6696,7 @@ A jump table for the options with a short description can be found at |Q_op|. Alias for 'term', see above. *'undodir'* *'udir'* *E926* -'undodir' 'udir' string (default ".") +'undodir' 'udir' string (default "$XDG_DATA_HOME/nvim/undo") global {only when compiled with the |+persistent_undo| feature} List of directory names for undo files, separated with commas.