diff options
Diffstat (limited to 'src/nvim/fileio.c')
-rw-r--r-- | src/nvim/fileio.c | 939 |
1 files changed, 536 insertions, 403 deletions
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index a7472b40e2..db1469db97 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.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. - */ - -/* * fileio.c: read from and write to a file */ @@ -15,6 +7,7 @@ #include <stdbool.h> #include <string.h> #include <inttypes.h> +#include <fcntl.h> #include "nvim/vim.h" #include "nvim/ascii.h" @@ -52,7 +45,6 @@ #include "nvim/search.h" #include "nvim/sha256.h" #include "nvim/strings.h" -#include "nvim/tempfile.h" #include "nvim/ui.h" #include "nvim/types.h" #include "nvim/undo.h" @@ -374,14 +366,13 @@ readfile ( /* don't display the file info for another buffer now */ need_fileinfo = FALSE; - /* - * For Unix: Use the short file name whenever possible. - * Avoids problems with networks and when directory names are changed. - * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to - * another directory, which we don't detect. - */ - if (sfname == NULL) + // For Unix: Use the short file name whenever possible. + // Avoids problems with networks and when directory names are changed. + // Don't do this for Windows, a "cd" in a sub-shell may have moved us to + // another directory, which we don't detect. + if (sfname == NULL) { sfname = fname; + } #if defined(UNIX) fname = sfname; #endif @@ -535,11 +526,7 @@ readfile ( if (!newfile) { return FAIL; } - if (perm < 0 -#ifdef ENOENT - && errno == ENOENT -#endif - ) { + if (perm == UV_ENOENT) { /* * Set the 'new-file' flag, so that when the file has * been created by someone else, a ":w" will complain. @@ -582,11 +569,11 @@ readfile ( return OK; /* a new file is not an error */ } else { filemess(curbuf, sfname, (char_u *)( -# ifdef EFBIG - (errno == EFBIG) ? _("[File too big]") : -# endif -# ifdef EOVERFLOW - (errno == EOVERFLOW) ? _("[File too big]") : + (fd == UV_EFBIG) ? _("[File too big]") : +# if defined(UNIX) && defined(EOVERFLOW) + // libuv only returns -errno in Unix and in Windows open() does not + // set EOVERFLOW + (fd == -EOVERFLOW) ? _("[File too big]") : # endif _("[Permission Denied]")), 0); curbuf->b_p_ro = TRUE; /* must use "w!" now */ @@ -618,13 +605,14 @@ readfile ( * Don't do this for a "nofile" or "nowrite" buffer type. */ if (!bt_dontwrite(curbuf)) { check_need_swap(newfile); - if (!read_stdin && (curbuf != old_curbuf - || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) - || (using_b_fname && - (old_b_fname != curbuf->b_fname)))) { + if (!read_stdin + && (curbuf != old_curbuf + || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) + || (using_b_fname && (old_b_fname != curbuf->b_fname)))) { EMSG(_(e_auchangedbuf)); - if (!read_buffer) + if (!read_buffer) { close(fd); + } return FAIL; } #ifdef UNIX @@ -1555,6 +1543,11 @@ rewind_retry: if (fileformat == EOL_UNKNOWN) { /* First try finding a NL, for Dos and Unix */ if (try_dos || try_unix) { + // Reset the carriage return counter. + if (try_mac) { + try_mac = 1; + } + for (p = ptr; p < ptr + size; ++p) { if (*p == NL) { if (!try_unix @@ -1563,6 +1556,8 @@ rewind_retry: else fileformat = EOL_UNIX; break; + } else if (*p == CAR && try_mac) { + try_mac++; } } @@ -1583,6 +1578,10 @@ rewind_retry: if (try_mac > try_unix) fileformat = EOL_MAC; } + } else if (fileformat == EOL_UNKNOWN && try_mac == 1) { + // Looking for CR but found no end-of-line markers at all: + // use the default format. + fileformat = default_fileformat(); } } @@ -1879,7 +1878,8 @@ failed: } if (msg_add_fileformat(fileformat)) c = TRUE; - msg_add_lines(c, (long)linecnt, filesize); + + msg_add_lines(c, (long)linecnt, filesize); xfree(keep_msg); keep_msg = NULL; @@ -1934,10 +1934,10 @@ failed: check_marks_read(); /* - * Trick: We remember if the last line of the read didn't have - * an eol even when 'binary' is off, for when writing it again with - * 'binary' on. This is required for - * ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work. + * We remember if the last line of the read didn't have + * an eol even when 'binary' is off, to support turning 'fixeol' off, + * or writing the read again with 'binary' on. The latter is required + * for ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work. */ curbuf->b_no_eol_lnum = read_no_eol_lnum; @@ -1992,13 +1992,14 @@ failed: } #ifdef OPEN_CHR_FILES -/* - * Returns TRUE if the file name argument is of the form "/dev/fd/\d\+", - * which is the name of files used for process substitution output by - * some shells on some operating systems, e.g., bash on SunOS. - * Do not accept "/dev/fd/[012]", opening these may hang Vim. - */ -static int is_dev_fd_file(char_u *fname) +/// Returns true if the file name argument is of the form "/dev/fd/\d\+", +/// which is the name of files used for process substitution output by +/// some shells on some operating systems, e.g., bash on SunOS. +/// Do not accept "/dev/fd/[012]", opening these may hang Vim. +/// +/// @param fname file name to check +static bool is_dev_fd_file(char_u *fname) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { return STRNCMP(fname, "/dev/fd/", 8) == 0 && ascii_isdigit(fname[8]) @@ -2138,9 +2139,10 @@ readfile_charconvert ( else { close(*fdp); /* close the input file, ignore errors */ *fdp = -1; - if (eval_charconvert(fenc, enc_utf8 ? (char_u *)"utf-8" : p_enc, - fname, tmpname) == FAIL) + if (eval_charconvert((char *) fenc, enc_utf8 ? "utf-8" : (char *) p_enc, + (char *) fname, (char *) tmpname) == FAIL) { errmsg = (char_u *)_("Conversion with 'charconvert' failed"); + } if (errmsg == NULL && (*fdp = os_open((char *)tmpname, O_RDONLY, 0)) < 0) { errmsg = (char_u *)_("can't read output of 'charconvert'"); } @@ -2353,13 +2355,12 @@ buf_write ( if (sfname == NULL) sfname = fname; - /* - * For Unix: Use the short file name whenever possible. - * Avoids problems with networks and when directory names are changed. - * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to - * another directory, which we don't detect - */ - ffname = fname; /* remember full fname */ + + // For Unix: Use the short file name whenever possible. + // Avoids problems with networks and when directory names are changed. + // Don't do this for Windows, a "cd" in a sub-shell may have moved us to + // another directory, which we don't detect. + ffname = fname; // remember full fname #ifdef UNIX fname = sfname; #endif @@ -2576,7 +2577,7 @@ buf_write ( errmsg = (char_u *)_("is a directory"); goto fail; } - if (mch_nodetype(fname) != NODE_WRITABLE) { + if (os_nodetype((char *)fname) != NODE_WRITABLE) { errnum = (char_u *)"E503: "; errmsg = (char_u *)_("is not a file or writable device"); goto fail; @@ -2588,11 +2589,11 @@ buf_write ( perm = -1; } } -#else /* !UNIX */ +#else /* win32 */ /* * Check for a writable device name. */ - c = mch_nodetype(fname); + c = os_nodetype((char *)fname); if (c == NODE_OTHER) { errnum = (char_u *)"E503: "; errmsg = (char_u *)_("is not a file or writable device"); @@ -2688,7 +2689,6 @@ buf_write ( } else if ((bkc & BKC_AUTO)) { /* "auto" */ int i; -# ifdef UNIX /* * Don't rename the file when: * - it's a hard link @@ -2699,9 +2699,7 @@ buf_write ( || !os_fileinfo_link((char *)fname, &file_info) || !os_fileinfo_id_equal(&file_info, &file_info_old)) { backup_copy = TRUE; - } else -# endif - { + } else { /* * Check if we can create a file and set the owner/group to * the ones from the original file. @@ -3322,7 +3320,7 @@ restore_backup: /* write failed or last line has no EOL: stop here */ if (end == 0 || (lnum == end - && write_bin + && (write_bin || !buf->b_p_fixeol) && (lnum == buf->b_no_eol_lnum || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) { ++lnum; /* written the line, count it */ @@ -3369,16 +3367,16 @@ restore_backup: nchars += len; } -#if defined(UNIX) && defined(HAVE_FSYNC) - /* On many journalling file systems there is a bug that causes both the - * original and the backup file to be lost when halting the system right - * after writing the file. That's because only the meta-data is - * journalled. Syncing the file slows down the system, but assures it has - * been written to disk and we don't lose it. - * For a device do try the fsync() but don't complain if it does not work - * (could be a pipe). - * If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. */ - if (p_fs && fsync(fd) != 0 && !device) { +#if defined(UNIX) + // On many journalling file systems there is a bug that causes both the + // original and the backup file to be lost when halting the system right + // after writing the file. That's because only the meta-data is + // journalled. Syncing the file slows down the system, but assures it has + // been written to disk and we don't lose it. + // For a device do try the fsync() but don't complain if it does not work + // (could be a pipe). + // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. + if (p_fs && os_fsync(fd) != 0 && !device) { errmsg = (char_u *)_("E667: Fsync failed"); end = 0; } @@ -3435,9 +3433,9 @@ restore_backup: * with 'charconvert' to (overwrite) the output file. */ if (end != 0) { - if (eval_charconvert(enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc, - wfname, fname) == FAIL) { - write_info.bw_conv_error = TRUE; + if (eval_charconvert(enc_utf8 ? "utf-8" : (char *) p_enc, (char *) fenc, + (char *) wfname, (char *) fname) == FAIL) { + write_info.bw_conv_error = true; end = 0; } } @@ -3807,29 +3805,30 @@ void msg_add_fname(buf_T *buf, char_u *fname) STRCAT(IObuff, "\" "); } -/* - * Append message for text mode to IObuff. - * Return TRUE if something appended. - */ -static int msg_add_fileformat(int eol_type) +/// Append message for text mode to IObuff. +/// +/// @param eol_type line ending type +/// +/// @return true if something was appended. +static bool msg_add_fileformat(int eol_type) { #ifndef USE_CRNL if (eol_type == EOL_DOS) { STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]")); - return TRUE; + return true; } #endif if (eol_type == EOL_MAC) { STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]")); - return TRUE; + return true; } #ifdef USE_CRNL if (eol_type == EOL_UNIX) { STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]")); - return TRUE; + return true; } #endif - return FALSE; + return false; } /* @@ -3893,7 +3892,11 @@ static int check_mtime(buf_T *buf, FileInfo *file_info) return OK; } -static int time_differs(long t1, long t2) +/// Return true if the times differ +/// +/// @param t1 first time +/// @param t2 second time +static bool time_differs(long t1, long t2) FUNC_ATTR_CONST { #if defined(__linux__) || defined(MSWIN) /* On a FAT filesystem, esp. under Linux, there are only 5 bits to store @@ -4082,19 +4085,17 @@ static int buf_write_bytes(struct bw_info *ip) return (wlen < len) ? FAIL : OK; } -/* - * Convert a Unicode character to bytes. - * Return TRUE for an error, FALSE when it's OK. - */ -static int -ucs2bytes ( - unsigned c, /* in: character */ - char_u **pp, /* in/out: pointer to result */ - int flags /* FIO_ flags */ -) +/// Convert a Unicode character to bytes. +/// +/// @param c character to convert +/// @param[in,out] pp pointer to store the result at +/// @param flags FIO_ flags that specify which encoding to use +/// +/// @return true for an error, false when it's OK. +static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL { char_u *p = *pp; - int error = FALSE; + bool error = false; int cc; @@ -4116,8 +4117,9 @@ ucs2bytes ( /* Make two words, ten bits of the character in each. First * word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff */ c -= 0x10000; - if (c >= 0x100000) - error = TRUE; + if (c >= 0x100000) { + error = true; + } cc = ((c >> 10) & 0x3ff) + 0xd800; if (flags & FIO_ENDIAN_L) { *p++ = cc; @@ -4127,8 +4129,9 @@ ucs2bytes ( *p++ = cc; } c = (c & 0x3ff) + 0xdc00; - } else - error = TRUE; + } else { + error = true; + } } if (flags & FIO_ENDIAN_L) { *p++ = c; @@ -4139,7 +4142,7 @@ ucs2bytes ( } } else { /* Latin1 */ if (c >= 0x100) { - error = TRUE; + error = true; *p++ = 0xBF; } else *p++ = c; @@ -4149,11 +4152,14 @@ ucs2bytes ( return error; } -/* - * Return TRUE if file encoding "fenc" requires conversion from or to - * 'encoding'. - */ -static int need_conversion(char_u *fenc) +/// Return true if file encoding "fenc" requires conversion from or to +/// 'encoding'. +/// +/// @param fenc file encoding to check +/// +/// @return true if conversion is required +static bool need_conversion(const char_u *fenc) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { int same_encoding; int enc_flags; @@ -4180,19 +4186,19 @@ static int need_conversion(char_u *fenc) return !(enc_utf8 && fenc_flags == FIO_UTF8); } -/* - * Check "ptr" for a unicode encoding and return the FIO_ flags needed for the - * internal conversion. - * if "ptr" is an empty string, use 'encoding'. - */ -static int get_fio_flags(char_u *ptr) +/// Return the FIO_ flags needed for the internal conversion if 'name' was +/// unicode or latin1, otherwise 0. If "name" is an empty string, +/// use 'encoding'. +/// +/// @param name string to check for encoding +static int get_fio_flags(const char_u *name) { int prop; - if (*ptr == NUL) - ptr = p_enc; - - prop = enc_canon_props(ptr); + if (*name == NUL) { + name = p_enc; + } + prop = enc_canon_props(name); if (prop & ENC_UNICODE) { if (prop & ENC_2BYTE) { if (prop & ENC_ENDIAN_L) @@ -4343,8 +4349,6 @@ void shorten_fnames(int force) /// @return [allocated] - A new filename, made up from: /// * fname + ext, if fname not NULL. /// * current dir + ext, if fname is NULL. -/// On Windows, and if ext starts with ".", a "_" is -/// preprended to ext (for filename to be valid). /// Result is guaranteed to: /// * be ended by <ext>. /// * have a basename with at most BASENAMELEN chars: @@ -4366,8 +4370,8 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) // (we need the full path in case :cd is used). if (fname == NULL || *fname == NUL) { retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL - if (os_dirname((char_u *)retval, MAXPATHL) == FAIL || - (fnamelen = strlen(retval)) == 0) { + if (os_dirname((char_u *)retval, MAXPATHL) == FAIL + || (fnamelen = strlen(retval)) == 0) { xfree(retval); return NULL; } @@ -4398,15 +4402,6 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) char *s; s = ptr + strlen(ptr); -#if defined(WIN3264) - // If there is no file name, and the extension starts with '.', put a - // '_' before the dot, because just ".ext" may be invalid if it's on a - // FAT partition, and on HPFS it doesn't matter. - else if ((fname == NULL || *fname == NUL) && *ext == '.') { - *s++ = '_'; - } -#endif - // Append the extension. // ext can start with '.' and cannot exceed 3 more characters. strcpy(s, ext); @@ -4435,11 +4430,15 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) return retval; } -/* - * Like fgets(), but if the file line is too long, it is truncated and the - * rest of the line is thrown away. Returns TRUE for end-of-file. - */ -int vim_fgets(char_u *buf, int size, FILE *fp) +/// Like fgets(), but if the file line is too long, it is truncated and the +/// rest of the line is thrown away. +/// +/// @param[out] buf buffer to fill +/// @param size size of the buffer +/// @param fp file to read from +/// +/// @return true for end-of-file. +bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL { char *eof; #define FGETS_SIZE 200 @@ -4739,7 +4738,6 @@ buf_check_timestamp ( { int retval = 0; char_u *path; - char_u *tbuf; char *mesg = NULL; char *mesg2 = ""; int helpmesg = FALSE; @@ -4809,19 +4807,17 @@ buf_check_timestamp ( else reason = "time"; - /* - * Only give the warning if there are no FileChangedShell - * autocommands. - * Avoid being called recursively by setting "busy". - */ - busy = TRUE; - set_vim_var_string(VV_FCS_REASON, (char_u *)reason, -1); - set_vim_var_string(VV_FCS_CHOICE, (char_u *)"", -1); - ++allbuf_lock; + // Only give the warning if there are no FileChangedShell + // autocommands. + // Avoid being called recursively by setting "busy". + busy = true; + set_vim_var_string(VV_FCS_REASON, reason, -1); + set_vim_var_string(VV_FCS_CHOICE, "", -1); + allbuf_lock++; n = apply_autocmds(EVENT_FILECHANGEDSHELL, - buf->b_fname, buf->b_fname, FALSE, buf); - --allbuf_lock; - busy = FALSE; + buf->b_fname, buf->b_fname, false, buf); + allbuf_lock--; + busy = false; if (n) { if (!buf_valid(buf)) EMSG(_("E246: FileChangedShell autocommand deleted buffer")); @@ -4875,35 +4871,39 @@ buf_check_timestamp ( if (mesg != NULL) { path = home_replace_save(buf, buf->b_fname); - if (!helpmesg) + if (!helpmesg) { mesg2 = ""; - tbuf = xmalloc(STRLEN(path) + STRLEN(mesg) + STRLEN(mesg2) + 2); - sprintf((char *)tbuf, mesg, path); - /* Set warningmsg here, before the unimportant and output-specific - * mesg2 has been appended. */ + } + const size_t tbuf_len = STRLEN(path) + STRLEN(mesg) + STRLEN(mesg2) + 2; + char *const tbuf = xmalloc(tbuf_len); + snprintf(tbuf, tbuf_len, mesg, path); + // Set warningmsg here, before the unimportant and output-specific + // mesg2 has been appended. set_vim_var_string(VV_WARNINGMSG, tbuf, -1); if (can_reload) { if (*mesg2 != NUL) { - STRCAT(tbuf, "\n"); - STRCAT(tbuf, mesg2); + strncat(tbuf, "\n", tbuf_len); + strncat(tbuf, mesg2, tbuf_len); + } + if (do_dialog(VIM_WARNING, (char_u *) _("Warning"), (char_u *) tbuf, + (char_u *) _("&OK\n&Load File"), 1, NULL, true) == 2) { + reload = true; } - if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), tbuf, - (char_u *)_("&OK\n&Load File"), 1, NULL, TRUE) == 2) - reload = TRUE; } else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) { if (*mesg2 != NUL) { - STRCAT(tbuf, "; "); - STRCAT(tbuf, mesg2); + strncat(tbuf, "; ", tbuf_len); + strncat(tbuf, mesg2, tbuf_len); } EMSG(tbuf); retval = 2; } else { if (!autocmd_busy) { msg_start(); - msg_puts_attr(tbuf, hl_attr(HLF_E) + MSG_HIST); - if (*mesg2 != NUL) + msg_puts_attr((char_u *) tbuf, hl_attr(HLF_E) + MSG_HIST); + if (*mesg2 != NUL) { msg_puts_attr((char_u *)mesg2, hl_attr(HLF_W) + MSG_HIST); + } msg_clr_eos(); (void)msg_end(); if (emsg_silent == 0) { @@ -5097,22 +5097,167 @@ void write_lnum_adjust(linenr_T offset) } #if defined(BACKSLASH_IN_FILENAME) -/* - * Convert all backslashes in fname to forward slashes in-place. - */ +/// Convert all backslashes in fname to forward slashes in-place, +/// unless when it looks like a URL. void forward_slash(char_u *fname) { char_u *p; - for (p = fname; *p != NUL; ++p) - /* The Big5 encoding can have '\' in the trail byte. */ - if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) - ++p; - else if (*p == '\\') + if (path_with_url(fname)) { + return; + } + for (p = fname; *p != NUL; p++) { + // The Big5 encoding can have '\' in the trail byte. + if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) { + p++; + } else if (*p == '\\') { *p = '/'; + } + } } #endif +/// Name of Vim's own temp dir. Ends in a slash. +static char_u *vim_tempdir = NULL; + +/// Create a directory for private use by this instance of Neovim. +/// This is done once, and the same directory is used for all temp files. +/// This method avoids security problems because of symlink attacks et al. +/// It's also a bit faster, because we only need to check for an existing +/// file when creating the directory and not for each temp file. +static void vim_maketempdir(void) +{ + static const char *temp_dirs[] = TEMP_DIR_NAMES; + // Try the entries in `TEMP_DIR_NAMES` to create the temp directory. + char_u template[TEMP_FILE_PATH_MAXLEN]; + char_u path[TEMP_FILE_PATH_MAXLEN]; + for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) { + // Expand environment variables, leave room for "/nvimXXXXXX/999999999" + expand_env((char_u *)temp_dirs[i], template, TEMP_FILE_PATH_MAXLEN - 22); + if (!os_isdir(template)) { // directory doesn't exist + continue; + } + + add_pathsep((char *)template); + // Concatenate with temporary directory name pattern + STRCAT(template, "nvimXXXXXX"); + + if (os_mkdtemp((const char *)template, (char *)path) != 0) { + continue; + } + + if (vim_settempdir((char *)path)) { + // Successfully created and set temporary directory so stop trying. + break; + } else { + // Couldn't set `vim_tempdir` to `path` so remove created directory. + os_rmdir((char *)path); + } + } +} + +/// Delete "name" and everything in it, recursively. +/// @param name The path which should be deleted. +/// @return 0 for success, -1 if some file was not deleted. +int delete_recursive(char_u *name) +{ + int result = 0; + + if (os_isrealdir(name)) { + snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT + + char_u **files; + int file_count; + char_u *exp = vim_strsave(NameBuff); + if (gen_expand_wildcards(1, &exp, &file_count, &files, + EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS + | EW_DODOT | EW_EMPTYOK) == OK) { + for (int i = 0; i < file_count; i++) { + if (delete_recursive(files[i]) != 0) { + result = -1; + } + } + FreeWild(file_count, files); + } else { + result = -1; + } + + xfree(exp); + os_rmdir((char *)name); + } else { + result = os_remove((char *)name) == 0 ? 0 : -1; + } + + return result; +} + +/// Delete the temp directory and all files it contains. +void vim_deltempdir(void) +{ + if (vim_tempdir != NULL) { + // remove the trailing path separator + path_tail(vim_tempdir)[-1] = NUL; + delete_recursive(vim_tempdir); + xfree(vim_tempdir); + vim_tempdir = NULL; + } +} + +/// Get the name of temp directory. This directory would be created on the first +/// call to this function. +char_u *vim_gettempdir(void) +{ + if (vim_tempdir == NULL) { + vim_maketempdir(); + } + + return vim_tempdir; +} + +/// Set Neovim own temporary directory name to `tempdir`. This directory should +/// be already created. Expand this name to a full path and put it in +/// `vim_tempdir`. This avoids that using `:cd` would confuse us. +/// +/// @param tempdir must be no longer than MAXPATHL. +/// +/// @return false if we run out of memory. +static bool vim_settempdir(char *tempdir) +{ + char *buf = verbose_try_malloc(MAXPATHL + 2); + if (!buf) { + return false; + } + vim_FullName(tempdir, buf, MAXPATHL, false); + add_pathsep(buf); + vim_tempdir = (char_u *)xstrdup(buf); + xfree(buf); + return true; +} + +/// Return a unique name that can be used for a temp file. +/// +/// @note The temp file is NOT created. +/// +/// @return pointer to the temp file name or NULL if Neovim can't create +/// temporary directory for its own temporary files. +char_u *vim_tempname(void) +{ + // Temp filename counter. + static uint32_t temp_count; + + char_u *tempdir = vim_gettempdir(); + if (!tempdir) { + return NULL; + } + + // There is no need to check if the file exists, because we own the directory + // and nobody else creates a file in it. + char_u template[TEMP_FILE_PATH_MAXLEN]; + snprintf((char *)template, TEMP_FILE_PATH_MAXLEN, + "%s%" PRIu32, tempdir, temp_count++); + return vim_strsave(template); +} + /* * Code for automatic commands. @@ -5338,11 +5483,13 @@ static void au_del_group(char_u *name) } } -/* - * Find the ID of an autocmd group name. - * Return it's ID. Returns AUGROUP_ERROR (< 0) for error. - */ -static int au_find_group(char_u *name) +/// Find the ID of an autocmd group name. +/// +/// @param name augroup name +/// +/// @return the ID or AUGROUP_ERROR (< 0) for error. +static int au_find_group(const char_u *name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { for (int i = 0; i < augroups.ga_len; ++i) { if (AUGROUP_NAME(i) != NULL && STRCMP(AUGROUP_NAME(i), name) == 0) { @@ -5352,10 +5499,11 @@ static int au_find_group(char_u *name) return AUGROUP_ERROR; } -/* - * Return TRUE if augroup "name" exists. - */ -int au_has_group(char_u *name) +/// Return true if augroup "name" exists. +/// +/// @param name augroup name +bool au_has_group(const char_u *name) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return au_find_group(name) != AUGROUP_ERROR; } @@ -5472,21 +5620,24 @@ find_end_event ( return pat; } -/* - * Return TRUE if "event" is included in 'eventignore'. - */ -static int event_ignored(event_T event) +/// Return true if "event" is included in 'eventignore'. +/// +/// @param event event to check +static bool event_ignored(event_T event) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *p = p_ei; + char_u *p = p_ei; while (*p != NUL) { - if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) - return TRUE; - if (event_name2nr(p, &p) == event) - return TRUE; + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { + return true; + } + if (event_name2nr(p, &p) == event) { + return true; + } } - return FALSE; + return false; } /* @@ -6003,18 +6154,19 @@ void ex_doautoall(exarg_T *eap) check_cursor(); /* just in case lines got deleted */ } -/* - * Check *argp for <nomodeline>. When it is present return FALSE, otherwise - * return TRUE and advance *argp to after it. - * Thus return TRUE when do_modelines() should be called. - */ -int check_nomodeline(char_u **argp) +/// Check *argp for <nomodeline>. When it is present return false, otherwise +/// return true and advance *argp to after it. Thus do_modelines() should be +/// called when true is returned. +/// +/// @param[in,out] argp argument string +bool check_nomodeline(char_u **argp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { if (STRNCMP(*argp, "<nomodeline>", 12) == 0) { *argp = skipwhite(*argp + 12); - return FALSE; + return false; } - return TRUE; + return true; } /* @@ -6191,76 +6343,78 @@ win_found: static int autocmd_nested = FALSE; -/* - * Execute autocommands for "event" and file name "fname". - * Return TRUE if some commands were executed. - */ -int -apply_autocmds ( - event_T event, - char_u *fname, /* NULL or empty means use actual file name */ - char_u *fname_io, /* fname to use for <afile> on cmdline */ - int force, /* when TRUE, ignore autocmd_busy */ - buf_T *buf /* buffer for <abuf> */ -) +/// Execute autocommands for "event" and file name "fname". +/// +/// @param event event that occured +/// @param fname filename, NULL or empty means use actual file name +/// @param fname_io filename to use for <afile> on cmdline +/// @param force When true, ignore autocmd_busy +/// @param buf Buffer for <abuf> +/// +/// @return true if some commands were executed. +bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force, + buf_T *buf) { return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, NULL); } -/* - * Like apply_autocmds(), but with extra "eap" argument. This takes care of - * setting v:filearg. - */ -static int apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, exarg_T *eap) +/// Like apply_autocmds(), but with extra "eap" argument. This takes care of +/// setting v:filearg. +/// +/// @param event event that occured +/// @param fname NULL or empty means use actual file name +/// @param fname_io fname to use for <afile> on cmdline +/// @param force When true, ignore autocmd_busy +/// @param buf Buffer for <abuf> +/// @param exarg Ex command arguments +/// +/// @return true if some commands were executed. +static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, + bool force, buf_T *buf, exarg_T *eap) { return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, eap); } -/* - * Like apply_autocmds(), but handles the caller's retval. If the script - * processing is being aborted or if retval is FAIL when inside a try - * conditional, no autocommands are executed. If otherwise the autocommands - * cause the script to be aborted, retval is set to FAIL. - */ -int -apply_autocmds_retval ( - event_T event, - char_u *fname, /* NULL or empty means use actual file name */ - char_u *fname_io, /* fname to use for <afile> on cmdline */ - int force, /* when TRUE, ignore autocmd_busy */ - buf_T *buf, /* buffer for <abuf> */ - int *retval /* pointer to caller's retval */ -) +/// Like apply_autocmds(), but handles the caller's retval. If the script +/// processing is being aborted or if retval is FAIL when inside a try +/// conditional, no autocommands are executed. If otherwise the autocommands +/// cause the script to be aborted, retval is set to FAIL. +/// +/// @param event event that occured +/// @param fname NULL or empty means use actual file name +/// @param fname_io fname to use for <afile> on cmdline +/// @param force When true, ignore autocmd_busy +/// @param buf Buffer for <abuf> +/// @param[in,out] retval caller's retval +/// +/// @return true if some autocommands were executed +bool apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io, + bool force, buf_T *buf, int *retval) { - int did_cmd; - - if (should_abort(*retval)) - return FALSE; + if (should_abort(*retval)) { + return false; + } - did_cmd = apply_autocmds_group(event, fname, fname_io, force, - AUGROUP_ALL, buf, NULL); - if (did_cmd - && aborting() - ) + bool did_cmd = apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, NULL); + if (did_cmd && aborting()) { *retval = FAIL; + } return did_cmd; } -/* - * Return TRUE when there is a CursorHold autocommand defined. - */ -int has_cursorhold(void) +/// Return true when there is a CursorHold/CursorHoldI autocommand defined for +/// the current mode. +bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return first_autopat[(int)(get_real_state() == NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL; } -/* - * Return TRUE if the CursorHold event can be triggered. - */ -int trigger_cursorhold(void) +/// Return true if the CursorHold/CursorHoldI event can be triggered. +bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { int state; @@ -6271,75 +6425,42 @@ int trigger_cursorhold(void) && !ins_compl_active() ) { state = get_real_state(); - if (state == NORMAL_BUSY || (state & INSERT) != 0) - return TRUE; + if (state == NORMAL_BUSY || (state & INSERT) != 0) { + return true; + } } - return FALSE; + return false; } -/* - * Return TRUE when there is a CursorMoved autocommand defined. - */ -int has_cursormoved(void) -{ - return first_autopat[(int)EVENT_CURSORMOVED] != NULL; -} - -/* - * Return TRUE when there is a CursorMovedI autocommand defined. - */ -int has_cursormovedI(void) -{ - return first_autopat[(int)EVENT_CURSORMOVEDI] != NULL; -} - -/* - * Return TRUE when there is a TextChanged autocommand defined. - */ -int has_textchanged(void) -{ - return first_autopat[(int)EVENT_TEXTCHANGED] != NULL; -} - -/* - * Return TRUE when there is a TextChangedI autocommand defined. - */ -int has_textchangedI(void) -{ - return first_autopat[(int)EVENT_TEXTCHANGEDI] != NULL; -} - -/* - * Return TRUE when there is an InsertCharPre autocommand defined. - */ -int has_insertcharpre(void) -{ - return first_autopat[(int)EVENT_INSERTCHARPRE] != NULL; -} - -/// @returns true when there is an CmdUndefined autocommand defined. -int has_cmdundefined(void) +/// Return true if "event" autocommand is defined. +/// +/// @param event the autocommand to check +bool has_event(int event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return first_autopat[(int)EVENT_CMDUNDEFINED] != NULL; + return first_autopat[event] != NULL; } -static int -apply_autocmds_group ( - event_T event, - char_u *fname, /* NULL or empty means use actual file name */ - char_u *fname_io, /* fname to use for <afile> on cmdline, NULL means - use fname */ - int force, /* when TRUE, ignore autocmd_busy */ - int group, /* group ID, or AUGROUP_ALL */ - buf_T *buf, /* buffer for <abuf> */ - exarg_T *eap /* command arguments */ -) +/// Execute autocommands for "event" and file name "fname". +/// +/// @param event event that occured +/// @param fname filename, NULL or empty means use actual file name +/// @param fname_io filename to use for <afile> on cmdline, +/// NULL means use `fname`. +/// @param force When true, ignore autocmd_busy +/// @param group autocmd group ID or AUGROUP_ALL +/// @param buf Buffer for <abuf> +/// @param eap Ex command arguments +/// +/// @return true if some commands were executed. +static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, + bool force, int group, buf_T *buf, + exarg_T *eap) { char_u *sfname = NULL; /* short file name */ char_u *tail; bool save_changed; buf_T *old_curbuf; - int retval = FALSE; + bool retval = false; char_u *save_sourcing_name; linenr_T save_sourcing_lnum; char_u *save_autocmd_fname; @@ -6430,7 +6551,7 @@ apply_autocmds_group ( * invalid. */ if (fname_io == NULL) { - if (event == EVENT_COLORSCHEME) + if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET) autocmd_fname = NULL; else if (fname != NULL && *fname != NUL) autocmd_fname = fname; @@ -6480,6 +6601,7 @@ apply_autocmds_group ( if (event == EVENT_COLORSCHEME || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED + || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY @@ -6492,17 +6614,16 @@ apply_autocmds_group ( } if (fname == NULL) { /* out of memory */ xfree(sfname); - retval = FALSE; + retval = false; goto BYPASS_AU; } #ifdef BACKSLASH_IN_FILENAME - /* - * Replace all backslashes with forward slashes. This makes the - * autocommand patterns portable between Unix and MS-DOS. - */ - if (sfname != NULL) + // Replace all backslashes with forward slashes. This makes the + // autocommand patterns portable between Unix and Windows. + if (sfname != NULL) { forward_slash(sfname); + } forward_slash(fname); #endif @@ -6576,17 +6697,19 @@ apply_autocmds_group ( if (eap != NULL) { save_cmdarg = set_cmdarg(eap, NULL); set_vim_var_nr(VV_CMDBANG, (long)eap->forceit); - } else - save_cmdarg = NULL; /* avoid gcc warning */ - retval = TRUE; - /* mark the last pattern, to avoid an endless loop when more patterns - * are added when executing autocommands */ - for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) - ap->last = FALSE; - ap->last = TRUE; - check_lnums(TRUE); /* make sure cursor and topline are valid */ + } else { + save_cmdarg = NULL; // avoid gcc warning + } + retval = true; + // mark the last pattern, to avoid an endless loop when more patterns + // are added when executing autocommands + for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) { + ap->last = false; + } + ap->last = true; + check_lnums(true); // make sure cursor and topline are valid do_cmdline(NULL, getnextac, (void *)&patcmd, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); set_vim_var_nr(VV_CMDBANG, save_cmdbang); @@ -6802,42 +6925,44 @@ char_u *getnextac(int c, void *cookie, int indent) return retval; } -/* - * Return TRUE if there is a matching autocommand for "fname". - * To account for buffer-local autocommands, function needs to know - * in which buffer the file will be opened. - */ -int has_autocmd(event_T event, char_u *sfname, buf_T *buf) +/// Return true if there is a matching autocommand for "fname". +/// To account for buffer-local autocommands, function needs to know +/// in which buffer the file will be opened. +/// +/// @param event event that occured. +/// @param sfname filename the event occured in. +/// @param buf buffer the file is open in +bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) + FUNC_ATTR_WARN_UNUSED_RESULT { AutoPat *ap; char_u *fname; char_u *tail = path_tail(sfname); - int retval = FALSE; + bool retval = false; - fname = (char_u *)FullName_save((char *)sfname, FALSE); - if (fname == NULL) - return FALSE; + fname = (char_u *)FullName_save((char *)sfname, false); + if (fname == NULL) { + return false; + } #ifdef BACKSLASH_IN_FILENAME - /* - * Replace all backslashes with forward slashes. This makes the - * autocommand patterns portable between Unix and MS-DOS. - */ + // Replace all backslashes with forward slashes. This makes the + // autocommand patterns portable between Unix and Windows. sfname = vim_strsave(sfname); forward_slash(sfname); forward_slash(fname); #endif - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { if (ap->pat != NULL && ap->cmds != NULL && (ap->buflocal_nr == 0 ? match_file_pat(NULL, &ap->reg_prog, fname, sfname, tail, ap->allow_dirs) - : buf != NULL && ap->buflocal_nr == buf->b_fnum - )) { - retval = TRUE; + : buf != NULL && ap->buflocal_nr == buf->b_fnum)) { + retval = true; break; } + } xfree(fname); #ifdef BACKSLASH_IN_FILENAME @@ -6925,29 +7050,27 @@ char_u *get_event_name(expand_T *xp, int idx) } -/* - * Return TRUE if autocmd is supported. - */ -int autocmd_supported(char_u *name) +/// Return true if autocmd "event" is supported. +bool autocmd_supported(char_u *event) { char_u *p; - return event_name2nr(name, &p) != NUM_EVENTS; + return event_name2nr(event, &p) != NUM_EVENTS; } -/* - * Return TRUE if an autocommand is defined for a group, event and - * pattern: The group can be omitted to accept any group. "event" and "pattern" - * can be NULL to accept any event and pattern. "pattern" can be NULL to accept - * any pattern. Buffer-local patterns <buffer> or <buffer=N> are accepted. - * Used for: - * exists("#Group") or - * exists("#Group#Event") or - * exists("#Group#Event#pat") or - * exists("#Event") or - * exists("#Event#pat") - */ -int au_exists(char_u *arg) +/// Return true if an autocommand is defined for a group, event and +/// pattern: The group can be omitted to accept any group. +/// `event` and `pattern` can be omitted to accept any event and pattern. +/// Buffer-local patterns <buffer> or <buffer=N> are accepted. +/// Used for: +/// exists("#Group") or +/// exists("#Group#Event") or +/// exists("#Group#Event#pat") or +/// exists("#Event") or +/// exists("#Event#pat") +/// +/// @param arg autocommand string +bool au_exists(const char_u *arg) FUNC_ATTR_WARN_UNUSED_RESULT { char_u *arg_save; char_u *pattern = NULL; @@ -6957,7 +7080,7 @@ int au_exists(char_u *arg) AutoPat *ap; buf_T *buflocal_buf = NULL; int group; - int retval = FALSE; + bool retval = false; /* Make a copy so that we can change the '#' chars to a NUL. */ arg_save = vim_strsave(arg); @@ -6973,8 +7096,8 @@ int au_exists(char_u *arg) event_name = arg_save; } else { if (p == NULL) { - /* "Group": group name is present and it's recognized */ - retval = TRUE; + // "Group": group name is present and it's recognized + retval = true; goto theend; } @@ -7016,7 +7139,7 @@ int au_exists(char_u *arg) || (buflocal_buf == NULL ? fnamecmp(ap->pat, pattern) == 0 : ap->buflocal_nr == buflocal_buf->b_fnum))) { - retval = TRUE; + retval = true; break; } @@ -7031,12 +7154,13 @@ theend: /// /// Used for autocommands and 'wildignore'. /// -/// @param pattern the pattern to match with -/// @param prog the pre-compiled regprog or NULL -/// @param fname the full path of the file name -/// @param sfname the short file name or NULL -/// @param tail the tail of the path -/// @param allow_dirs allow matching with dir +/// @param pattern pattern to match with +/// @param prog pre-compiled regprog or NULL +/// @param fname full path of the file name +/// @param sfname short file name or NULL +/// @param tail tail of the path +/// @param allow_dirs Allow matching with dir +/// /// @return true if there is a match, false otherwise static bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, char_u *sfname, char_u *tail, int allow_dirs) @@ -7075,12 +7199,17 @@ static bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, return result; } -/* - * Return TRUE if a file matches with a pattern in "list". - * "list" is a comma-separated list of patterns, like 'wildignore'. - * "sfname" is the short file name or NULL, "ffname" the long file name. - */ -int match_file_list(char_u *list, char_u *sfname, char_u *ffname) +/// Check if a file matches with a pattern in "list". +/// "list" is a comma-separated list of patterns, like 'wildignore'. +/// "sfname" is the short file name or NULL, "ffname" the long file name. +/// +/// @param list list of patterns to match +/// @param sfname short file name +/// @param ffname full file name +/// +/// @return true if there was a match +bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3) { char_u buf[100]; char_u *tail; @@ -7091,42 +7220,41 @@ int match_file_list(char_u *list, char_u *sfname, char_u *ffname) tail = path_tail(sfname); - /* try all patterns in 'wildignore' */ + // try all patterns in 'wildignore' p = list; while (*p) { - copy_option_part(&p, buf, 100, ","); - regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, FALSE); - if (regpat == NULL) + copy_option_part(&p, buf, ARRAY_SIZE(buf), ","); + regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, false); + if (regpat == NULL) { break; - match = match_file_pat(regpat, NULL, ffname, sfname, - tail, (int)allow_dirs); + } + match = match_file_pat(regpat, NULL, ffname, sfname, tail, (int)allow_dirs); xfree(regpat); - if (match) - return TRUE; + if (match) { + return true; + } } - return FALSE; + return false; } -/* - * Convert the given pattern "pat" which has shell style wildcards in it, into - * a regular expression, and return the result in allocated memory. If there - * is a directory path separator to be matched, then TRUE is put in - * allow_dirs, otherwise FALSE is put there -- webb. - * Handle backslashes before special characters, like "\*" and "\ ". - * - * Returns NULL on failure. - */ -char_u * -file_pat_to_reg_pat ( - char_u *pat, - char_u *pat_end, /* first char after pattern or NULL */ - char *allow_dirs, /* Result passed back out in here */ - int no_bslash /* Don't use a backward slash as pathsep */ +/// Convert the given pattern "pat" which has shell style wildcards in it, into +/// a regular expression, and return the result in allocated memory. If there +/// is a directory path separator to be matched, then TRUE is put in +/// allow_dirs, otherwise FALSE is put there -- webb. +/// Handle backslashes before special characters, like "\*" and "\ ". +/// +/// Returns NULL on failure. +char_u * file_pat_to_reg_pat( + const char_u *pat, + const char_u *pat_end, // first char after pattern or NULL + char *allow_dirs, // Result passed back out in here + int no_bslash // Don't use a backward slash as pathsep ) + FUNC_ATTR_NONNULL_ARG(1) { - char_u *endp; + const char_u *endp; char_u *reg_pat; - char_u *p; + const char_u *p; int nested = 0; int add_dollar = TRUE; @@ -7135,6 +7263,10 @@ file_pat_to_reg_pat ( if (pat_end == NULL) pat_end = pat + STRLEN(pat); + if (pat_end == pat) { + return (char_u *)xstrdup("^$"); + } + size_t size = 2; // '^' at start, '$' at end. for (p = pat; p < pat_end; p++) { @@ -7172,10 +7304,11 @@ file_pat_to_reg_pat ( else reg_pat[i++] = '^'; endp = pat_end - 1; - if (*endp == '*') { - while (endp - pat > 0 && *endp == '*') + if (endp >= pat && *endp == '*') { + while (endp - pat > 0 && *endp == '*') { endp--; - add_dollar = FALSE; + } + add_dollar = false; } for (p = pat; *p && nested >= 0 && p <= endp; p++) { switch (*p) { @@ -7230,12 +7363,12 @@ file_pat_to_reg_pat ( #ifdef BACKSLASH_IN_FILENAME && no_bslash #endif - ) + ) { reg_pat[i++] = '?'; - else if (*p == ',' || *p == '%' || *p == '#' - || *p == ' ' || *p == '{' || *p == '}') + } else if (*p == ',' || *p == '%' || *p == '#' + || ascii_isspace(*p) || *p == '{' || *p == '}') { reg_pat[i++] = *p; - else if (*p == '\\' && p[1] == '\\' && p[2] == '{') { + } else if (*p == '\\' && p[1] == '\\' && p[2] == '{') { reg_pat[i++] = '\\'; reg_pat[i++] = '{'; p += 2; |