aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/fileio.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/fileio.c')
-rw-r--r--src/nvim/fileio.c445
1 files changed, 255 insertions, 190 deletions
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 7573064fa9..d4407b4982 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -3,6 +3,8 @@
// fileio.c: read from and write to a file
+// uncrustify:off
+
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
@@ -20,6 +22,7 @@
#include "nvim/diff.h"
#include "nvim/edit.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
@@ -30,12 +33,12 @@
#include "nvim/getchar.h"
#include "nvim/hashtab.h"
#include "nvim/iconv.h"
+#include "nvim/input.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/misc1.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
@@ -77,7 +80,7 @@
#define FIO_ENDIAN_L 0x80 // little endian
#define FIO_NOCONVERT 0x2000 // skip encoding conversion
#define FIO_UCSBOM 0x4000 // check for BOM at start of file
-#define FIO_ALL -1 // allow all formats
+#define FIO_ALL (-1) // allow all formats
/* When converting, a read() or write() may leave some bytes to be converted
* for the next call. The value is guessed... */
@@ -100,8 +103,8 @@ struct bw_info {
char_u bw_rest[CONV_RESTLEN]; // not converted bytes
int bw_restlen; // nr of bytes in bw_rest[]
int bw_first; // first write call
- char_u *bw_conv_buf; // buffer for writing converted chars
- int bw_conv_buflen; // size of bw_conv_buf
+ char_u *bw_conv_buf; // buffer for writing converted chars
+ size_t bw_conv_buflen; // size of bw_conv_buf
int bw_conv_error; // set for conversion error
linenr_T bw_conv_error_lnum; // first line with error or zero
linenr_T bw_start_lnum; // line number at start of buffer
@@ -242,6 +245,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
bool notconverted = false; // true if conversion wanted but it wasn't possible
char_u conv_rest[CONV_RESTLEN];
int conv_restlen = 0; // nr of bytes in conv_rest[]
+ pos_T orig_start;
buf_T *old_curbuf;
char_u *old_b_ffname;
char_u *old_b_fname;
@@ -298,14 +302,10 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
fname = sfname;
#endif
- /*
- * The BufReadCmd and FileReadCmd events intercept the reading process by
- * executing the associated commands instead.
- */
+ // The BufReadCmd and FileReadCmd events intercept the reading process by
+ // executing the associated commands instead.
if (!filtering && !read_stdin && !read_buffer) {
- pos_T pos;
-
- pos = curbuf->b_op_start;
+ orig_start = curbuf->b_op_start;
// Set '[ mark to the line above where the lines go (line 1 if zero).
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
@@ -335,7 +335,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
return aborting() ? FAIL : OK;
}
- curbuf->b_op_start = pos;
+ curbuf->b_op_start = orig_start;
}
if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) {
@@ -362,7 +362,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0);
msg_end();
msg_scroll = msg_save;
- return FAIL;
+ return NOTDONE;
}
}
@@ -408,6 +408,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
if (os_fileinfo((char *)fname, &file_info)) {
buf_store_file_info(curbuf, &file_info);
curbuf->b_mtime_read = curbuf->b_mtime;
+ curbuf->b_mtime_read_ns = curbuf->b_mtime_ns;
#ifdef UNIX
/*
* Use the protection bits of the original file for the swap file.
@@ -424,7 +425,9 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
#endif
} else {
curbuf->b_mtime = 0;
+ curbuf->b_mtime_ns = 0;
curbuf->b_mtime_read = 0;
+ curbuf->b_mtime_read_ns = 0;
curbuf->b_orig_size = 0;
curbuf->b_orig_mode = 0;
}
@@ -576,9 +579,8 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
++no_wait_return; // don't wait for return yet
- /*
- * Set '[ mark to the line above where the lines go (line 1 if zero).
- */
+ // Set '[ mark to the line above where the lines go (line 1 if zero).
+ orig_start = curbuf->b_op_start;
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
curbuf->b_op_start.col = 0;
@@ -618,6 +620,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
try_mac = (vim_strchr(p_ffs, 'm') != NULL);
try_dos = (vim_strchr(p_ffs, 'd') != NULL);
try_unix = (vim_strchr(p_ffs, 'x') != NULL);
+ curbuf->b_op_start = orig_start;
if (msg_scrolled == n) {
msg_scroll = m;
@@ -1012,27 +1015,27 @@ retry:
}
read_buf_col += n;
break;
- } else {
- // Append whole line and new-line. Change NL
- // to NUL to reverse the effect done below.
- for (ni = 0; ni < n; ni++) {
- if (p[ni] == NL) {
- ptr[tlen++] = NUL;
- } else {
- ptr[tlen++] = p[ni];
- }
+ }
+
+ // Append whole line and new-line. Change NL
+ // to NUL to reverse the effect done below.
+ for (ni = 0; ni < n; ni++) {
+ if (p[ni] == NL) {
+ ptr[tlen++] = NUL;
+ } else {
+ ptr[tlen++] = p[ni];
}
- ptr[tlen++] = NL;
- read_buf_col = 0;
- if (++read_buf_lnum > from) {
- // When the last line didn't have an
- // end-of-line don't add it now either.
- if (!curbuf->b_p_eol) {
- --tlen;
- }
- size = tlen;
- break;
+ }
+ ptr[tlen++] = NL;
+ read_buf_col = 0;
+ if (++read_buf_lnum > from) {
+ // When the last line didn't have an
+ // end-of-line don't add it now either.
+ if (!curbuf->b_p_eol) {
+ tlen--;
}
+ size = tlen;
+ break;
}
}
}
@@ -1360,6 +1363,10 @@ retry:
u8c += (unsigned)(*--p) << 16;
u8c += (unsigned)(*--p) << 24;
}
+ // Replace characters over INT_MAX with Unicode replacement character
+ if (u8c > INT_MAX) {
+ u8c = 0xfffd;
+ }
} else { // UTF-8
if (*--p < 0x80) {
u8c = *p;
@@ -1847,7 +1854,7 @@ failed:
msg_scrolled_ign = true;
if (!read_stdin && !read_buffer) {
- p = (char_u *)msg_trunc_attr((char *)IObuff, FALSE, 0);
+ p = (char_u *)msg_trunc_attr((char *)IObuff, false, 0);
}
if (read_stdin || read_buffer || restart_edit != 0
@@ -1884,13 +1891,13 @@ failed:
check_cursor_lnum();
beginline(BL_WHITE | BL_FIX); // on first non-blank
- /*
- * Set '[ and '] marks to the newly read lines.
- */
- curbuf->b_op_start.lnum = from + 1;
- curbuf->b_op_start.col = 0;
- curbuf->b_op_end.lnum = from + linecnt;
- curbuf->b_op_end.col = 0;
+ if (!cmdmod.lockmarks) {
+ // Set '[ and '] marks to the newly read lines.
+ curbuf->b_op_start.lnum = from + 1;
+ curbuf->b_op_start.col = 0;
+ curbuf->b_op_end.lnum = from + linecnt;
+ curbuf->b_op_end.col = 0;
+ }
}
msg_scroll = msg_save;
@@ -2009,10 +2016,8 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp)
return lnum;
}
-/*
- * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be
- * equal to the buffer "buf". Used for calling readfile().
- */
+/// Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary' to be
+/// equal to the buffer "buf". Used for calling readfile().
void prep_exarg(exarg_T *eap, const buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -2029,9 +2034,7 @@ void prep_exarg(exarg_T *eap, const buf_T *buf)
eap->forceit = FALSE;
}
-/*
- * Set default or forced 'fileformat' and 'binary'.
- */
+/// Set default or forced 'fileformat' and 'binary'.
void set_file_options(int set_options, exarg_T *eap)
{
// set default 'fileformat'
@@ -2052,9 +2055,7 @@ void set_file_options(int set_options, exarg_T *eap)
}
}
-/*
- * Set forced 'fileencoding'.
- */
+/// Set forced 'fileencoding'.
void set_forced_fenc(exarg_T *eap)
{
if (eap->force_enc != 0) {
@@ -2064,12 +2065,12 @@ void set_forced_fenc(exarg_T *eap)
}
}
-// Find next fileencoding to use from 'fileencodings'.
-// "pp" points to fenc_next. It's advanced to the next item.
-// When there are no more items, an empty string is returned and *pp is set to
-// NULL.
-// When *pp is not set to NULL, the result is in allocated memory and "alloced"
-// is set to true.
+/// Find next fileencoding to use from 'fileencodings'.
+/// "pp" points to fenc_next. It's advanced to the next item.
+/// When there are no more items, an empty string is returned and *pp is set to
+/// NULL.
+/// When *pp is not set to NULL, the result is in allocated memory and "alloced"
+/// is set to true.
static char_u *next_fenc(char_u **pp, bool *alloced)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -2145,10 +2146,8 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp)
}
-/*
- * Read marks for the current buffer from the ShaDa file, when we support
- * buffer marks and the buffer has a name.
- */
+/// Read marks for the current buffer from the ShaDa file, when we support
+/// buffer marks and the buffer has a name.
static void check_marks_read(void)
{
if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0
@@ -2248,6 +2247,8 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
int write_undo_file = FALSE;
context_sha256_T sha_ctx;
unsigned int bkc = get_bkc_value(buf);
+ const pos_T orig_start = buf->b_op_start;
+ const pos_T orig_end = buf->b_op_end;
if (fname == NULL || *fname == NUL) { // safety check
return FAIL;
@@ -2428,7 +2429,13 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
|| did_cmd || nofile_err
|| aborting()) {
- --no_wait_return;
+ if (buf != NULL && cmdmod.lockmarks) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ no_wait_return--;
msg_scroll = msg_save;
if (nofile_err) {
emsg(_("E676: No matching autocommands for acwrite buffer"));
@@ -2509,6 +2516,11 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_
}
}
+ if (cmdmod.lockmarks) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
if (shortmess(SHM_OVER) && !exiting) {
msg_scroll = FALSE; // overwrite previous file message
@@ -3304,7 +3316,7 @@ restore_backup:
if (end == 0
|| (lnum == end
&& (write_bin || !buf->b_p_fixeol)
- && (lnum == buf->b_no_eol_lnum
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
lnum++; // written the line, count it
no_eol = true;
@@ -3681,11 +3693,12 @@ nofail:
msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
attr | MSG_HIST);
- /* Update the timestamp to avoid an "overwrite changed file"
- * prompt when writing again. */
+ // Update the timestamp to avoid an "overwrite changed file"
+ // prompt when writing again.
if (os_fileinfo((char *)fname, &file_info_old)) {
buf_store_file_info(buf, &file_info_old);
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
}
}
}
@@ -3743,10 +3756,8 @@ nofail:
#undef SET_ERRMSG_NUM
}
-/*
- * Set the name of the current buffer. Use when the buffer doesn't have a
- * name and a ":r" or ":w" command with a file name is used.
- */
+/// Set the name of the current buffer. Use when the buffer doesn't have a
+/// name and a ":r" or ":w" command with a file name is used.
static int set_rw_fname(char_u *fname, char_u *sfname)
{
buf_T *buf = curbuf;
@@ -3780,7 +3791,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
// Do filetype detection now if 'filetype' is empty.
if (*curbuf->b_p_ft == NUL) {
- if (au_has_group((char_u *)"filetypedetect")) {
+ if (augroup_exists("filetypedetect")) {
(void)do_doautocmd((char_u *)"filetypedetect BufRead", false, NULL);
}
do_modelines(0);
@@ -3836,9 +3847,7 @@ static bool msg_add_fileformat(int eol_type)
return false;
}
-/*
- * Append line and character count to IObuff.
- */
+/// Append line and character count to IObuff.
void msg_add_lines(int insert_space, long lnum, off_T nchars)
{
char_u *p;
@@ -3849,7 +3858,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
*p++ = ' ';
}
if (shortmess(SHM_LINES)) {
- vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "C",
+ vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "B",
(int64_t)lnum, (int64_t)nchars);
} else {
vim_snprintf((char *)p, IOSIZE - (p - IObuff),
@@ -3857,30 +3866,25 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
(int64_t)lnum);
p += STRLEN(p);
vim_snprintf((char *)p, IOSIZE - (p - IObuff),
- NGETTEXT("%" PRId64 " character", "%" PRId64 " characters", nchars),
+ NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars),
(int64_t)nchars);
}
}
-/*
- * Append message for missing line separator to IObuff.
- */
+/// Append message for missing line separator to IObuff.
static void msg_add_eol(void)
{
STRCAT(IObuff,
shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"));
}
-/*
- * Check modification time of file, before writing to it.
- * The size isn't checked, because using a tool like "gzip" takes care of
- * using the same timestamp but can't set the size.
- */
+/// Check modification time of file, before writing to it.
+/// The size isn't checked, because using a tool like "gzip" takes care of
+/// using the same timestamp but can't set the size.
static int check_mtime(buf_T *buf, FileInfo *file_info)
{
if (buf->b_mtime_read != 0
- && time_differs(file_info->stat.st_mtim.tv_sec,
- buf->b_mtime_read)) {
+ && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
msg_scroll = true; // Don't overwrite messages here.
msg_silent = 0; // Must give this prompt.
// Don't use emsg() here, don't want to flush the buffers.
@@ -3894,28 +3898,24 @@ static int check_mtime(buf_T *buf, FileInfo *file_info)
return OK;
}
-/// 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
+static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST
{
+ return file_info->stat.st_mtim.tv_nsec != mtime_ns
#if defined(__linux__) || defined(MSWIN)
- // On a FAT filesystem, esp. under Linux, there are only 5 bits to store
- // the seconds. Since the roundoff is done when flushing the inode, the
- // time may change unexpectedly by one second!!!
- return t1 - t2 > 1 || t2 - t1 > 1;
+ // On a FAT filesystem, esp. under Linux, there are only 5 bits to store
+ // the seconds. Since the roundoff is done when flushing the inode, the
+ // time may change unexpectedly by one second!!!
+ || file_info->stat.st_mtim.tv_sec - mtime > 1
+ || mtime - file_info->stat.st_mtim.tv_sec > 1;
#else
- return t1 != t2;
+ || (long)file_info->stat.st_mtim.tv_sec != mtime;
#endif
}
-/*
- * Call write() to write a number of bytes to the file.
- * Handles 'encoding' conversion.
- *
- * Return FAIL for failure, OK otherwise.
- */
+/// Call write() to write a number of bytes to the file.
+/// Handles 'encoding' conversion.
+///
+/// @return FAIL for failure, OK otherwise.
static int buf_write_bytes(struct bw_info *ip)
{
int wlen;
@@ -4243,12 +4243,11 @@ static int get_fio_flags(const char_u *name)
}
-/*
- * Check for a Unicode BOM (Byte Order Mark) at the start of p[size].
- * "size" must be at least 2.
- * Return the name of the encoding and set "*lenp" to the length.
- * Returns NULL when no BOM found.
- */
+/// Check for a Unicode BOM (Byte Order Mark) at the start of p[size].
+/// "size" must be at least 2.
+///
+/// @return the name of the encoding and set "*lenp" to the length or,
+/// NULL when no BOM found.
static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags)
{
char *name = NULL;
@@ -4289,10 +4288,9 @@ static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags)
return (char_u *)name;
}
-/*
- * Generate a BOM in "buf[4]" for encoding "name".
- * Return the length of the BOM (zero when no BOM).
- */
+/// Generate a BOM in "buf[4]" for encoding "name".
+///
+/// @return the length of the BOM (zero when no BOM).
static int make_bom(char_u *buf, char_u *name)
{
int flags;
@@ -4317,10 +4315,12 @@ static int make_bom(char_u *buf, char_u *name)
}
/// Shorten filename of a buffer.
-/// When "force" is TRUE: Use full path from now on for files currently being
-/// edited, both for file name and swap file name. Try to shorten the file
-/// names a bit, if safe to do so.
-/// When "force" is FALSE: Only try to shorten absolute file names.
+///
+/// @param force when TRUE: Use full path from now on for files currently being
+/// edited, both for file name and swap file name. Try to shorten the file
+/// names a bit, if safe to do so.
+/// when FALSE: Only try to shorten absolute file names.
+///
/// For buffers that have buftype "nofile" or "scratch": never change the file
/// name.
void shorten_buf_fname(buf_T *buf, char_u *dirname, int force)
@@ -4498,7 +4498,8 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL
}
/// Read 2 bytes from "fd" and turn them into an int, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
int get2c(FILE *fd)
{
const int n = getc(fd);
@@ -4513,7 +4514,8 @@ int get2c(FILE *fd)
}
/// Read 3 bytes from "fd" and turn them into an int, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
int get3c(FILE *fd)
{
int n = getc(fd);
@@ -4533,7 +4535,8 @@ int get3c(FILE *fd)
}
/// Read 4 bytes from "fd" and turn them into an int, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
int get4c(FILE *fd)
{
// Use unsigned rather than int otherwise result is undefined
@@ -4564,7 +4567,8 @@ int get4c(FILE *fd)
}
/// Read 8 bytes from `fd` and turn them into a time_t, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
time_t get8ctime(FILE *fd)
{
time_t n = 0;
@@ -4580,7 +4584,8 @@ time_t get8ctime(FILE *fd)
}
/// Reads a string of length "cnt" from "fd" into allocated memory.
-/// @return pointer to the string or NULL when unable to read that many bytes.
+///
+/// @return pointer to the string or NULL when unable to read that many bytes.
char *read_string(FILE *fd, size_t cnt)
{
char *str = xmallocz(cnt);
@@ -4596,7 +4601,8 @@ char *read_string(FILE *fd, size_t cnt)
}
/// Writes a number to file "fd", most significant bit first, in "len" bytes.
-/// @returns false in case of an error.
+///
+/// @return false in case of an error.
bool put_bytes(FILE *fd, uintmax_t number, size_t len)
{
assert(len > 0);
@@ -4609,7 +4615,8 @@ bool put_bytes(FILE *fd, uintmax_t number, size_t len)
}
/// Writes time_t to file "fd" in 8 bytes.
-/// @returns FAIL when the write failed.
+///
+/// @return FAIL when the write failed.
int put_time(FILE *fd, time_t time_)
{
uint8_t buf[8];
@@ -4620,7 +4627,7 @@ int put_time(FILE *fd, time_t time_)
/// os_rename() only works if both files are on the same file system, this
/// function will (attempts to?) copy the file across if rename fails -- webb
///
-/// @return -1 for failure, 0 for success
+/// @return -1 for failure, 0 for success
int vim_rename(const char_u *from, const char_u *to)
FUNC_ATTR_NONNULL_ALL
{
@@ -4845,11 +4852,10 @@ int check_timestamps(int focus)
return didit;
}
-/*
- * Move all the lines from buffer "frombuf" to buffer "tobuf".
- * Return OK or FAIL. When FAIL "tobuf" is incomplete and/or "frombuf" is not
- * empty.
- */
+/// Move all the lines from buffer "frombuf" to buffer "tobuf".
+///
+/// @return OK or FAIL.
+/// When FAIL "tobuf" is incomplete and/or "frombuf" is not empty.
static int move_lines(buf_T *frombuf, buf_T *tobuf)
{
buf_T *tbuf = curbuf;
@@ -4886,13 +4892,12 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
return retval;
}
-/*
- * Check if buffer "buf" has been changed.
- * Also check if the file for a new buffer unexpectedly appeared.
- * return 1 if a changed buffer was found.
- * return 2 if a message has been displayed.
- * return 0 otherwise.
- */
+/// Check if buffer "buf" has been changed.
+/// Also check if the file for a new buffer unexpectedly appeared.
+///
+/// @return 1 if a changed buffer was found or,
+/// 2 if a message has been displayed or,
+/// 0 otherwise.
int buf_check_timestamp(buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -4901,7 +4906,11 @@ int buf_check_timestamp(buf_T *buf)
char *mesg = NULL;
char *mesg2 = "";
bool helpmesg = false;
- bool reload = false;
+ enum {
+ RELOAD_NONE,
+ RELOAD_NORMAL,
+ RELOAD_DETECT
+ } reload = RELOAD_NONE;
bool can_reload = false;
uint64_t orig_size = buf->b_orig_size;
int orig_mode = buf->b_orig_mode;
@@ -4929,7 +4938,7 @@ int buf_check_timestamp(buf_T *buf)
if (!(buf->b_flags & BF_NOTEDITED)
&& buf->b_mtime != 0
&& (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info))
- || time_differs(file_info.stat.st_mtim.tv_sec, buf->b_mtime)
+ || time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns)
|| (int)file_info.stat.st_mode != buf->b_orig_mode)) {
const long prev_b_mtime = buf->b_mtime;
@@ -4946,15 +4955,14 @@ int buf_check_timestamp(buf_T *buf)
buf_store_file_info(buf, &file_info);
}
- // Don't do anything for a directory. Might contain the file
- // explorer.
if (os_isdir(buf->b_fname)) {
+ // Don't do anything for a directory. Might contain the file explorer.
} else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar)
&& !bufIsChanged(buf) && file_info_ok) {
// If 'autoread' is set, the buffer has no changes and the file still
// exists, reload the buffer. Use the buffer-local option value if it
// was set, the global option value otherwise.
- reload = true;
+ reload = RELOAD_NORMAL;
} else {
if (!file_info_ok) {
reason = "deleted";
@@ -4985,7 +4993,9 @@ int buf_check_timestamp(buf_T *buf)
}
s = get_vim_var_str(VV_FCS_CHOICE);
if (STRCMP(s, "reload") == 0 && *reason != 'd') {
- reload = true;
+ reload = RELOAD_NORMAL;
+ } else if (STRCMP(s, "edit") == 0) {
+ reload = RELOAD_DETECT;
} else if (STRCMP(s, "ask") == 0) {
n = false;
} else {
@@ -5020,6 +5030,7 @@ int buf_check_timestamp(buf_T *buf)
// Only timestamp changed, store it to avoid a warning
// in check_mtime() later.
buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
}
}
}
@@ -5048,9 +5059,15 @@ int buf_check_timestamp(buf_T *buf)
xstrlcat(tbuf, "\n", tbuf_len - 1);
xstrlcat(tbuf, mesg2, tbuf_len - 1);
}
- if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf,
- (char_u *)_("&OK\n&Load File"), 1, NULL, true) == 2) {
- reload = true;
+ switch (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf,
+ (char_u *)_("&OK\n&Load File\nLoad File &and Options"),
+ 1, NULL, true)) {
+ case 2:
+ reload = RELOAD_NORMAL;
+ break;
+ case 3:
+ reload = RELOAD_DETECT;
+ break;
}
} else if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned) {
if (*mesg2 != NUL) {
@@ -5084,9 +5101,9 @@ int buf_check_timestamp(buf_T *buf)
xfree(tbuf);
}
- if (reload) {
+ if (reload != RELOAD_NONE) {
// Reload the buffer.
- buf_reload(buf, orig_mode);
+ buf_reload(buf, orig_mode, reload == RELOAD_DETECT);
if (buf->b_p_udf && buf->b_ffname != NULL) {
char_u hash[UNDO_HASH_SIZE];
@@ -5104,13 +5121,11 @@ int buf_check_timestamp(buf_T *buf)
return retval;
}
-/*
- * Reload a buffer that is already loaded.
- * Used when the file was changed outside of Vim.
- * "orig_mode" is buf->b_orig_mode before the need for reloading was detected.
- * buf->b_orig_mode may have been reset already.
- */
-void buf_reload(buf_T *buf, int orig_mode)
+/// Reload a buffer that is already loaded.
+/// Used when the file was changed outside of Vim.
+/// "orig_mode" is buf->b_orig_mode before the need for reloading was detected.
+/// buf->b_orig_mode may have been reset already.
+void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
{
exarg_T ea;
pos_T old_cursor;
@@ -5125,11 +5140,15 @@ void buf_reload(buf_T *buf, int orig_mode)
// set curwin/curbuf for "buf" and save some things
aucmd_prepbuf(&aco, buf);
- // We only want to read the text from the file, not reset the syntax
- // highlighting, clear marks, diff status, etc. Force the fileformat and
- // encoding to be the same.
+ // Unless reload_options is set, we only want to read the text from the
+ // file, not reset the syntax highlighting, clear marks, diff status, etc.
+ // Force the fileformat and encoding to be the same.
+ if (reload_options) {
+ memset(&ea, 0, sizeof(ea));
+ } else {
+ prep_exarg(&ea, buf);
+ }
- prep_exarg(&ea, buf);
old_cursor = curwin->w_cursor;
old_topline = curwin->w_topline;
@@ -5248,14 +5267,13 @@ void buf_store_file_info(buf_T *buf, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
buf->b_mtime = file_info->stat.st_mtim.tv_sec;
+ buf->b_mtime_ns = file_info->stat.st_mtim.tv_nsec;
buf->b_orig_size = os_fileinfo_size(file_info);
buf->b_orig_mode = (int)file_info->stat.st_mode;
}
-/*
- * Adjust the line with missing eol, used for the next write.
- * Used for do_filter(), when the input lines for the filter are deleted.
- */
+/// Adjust the line with missing eol, used for the next write.
+/// Used for do_filter(), when the input lines for the filter are deleted.
void write_lnum_adjust(linenr_T offset)
{
if (curbuf->b_no_eol_lnum != 0) { // only if there is a missing eol
@@ -5326,35 +5344,85 @@ static void vim_maketempdir(void)
(void)umask(umask_save);
}
+/// Core part of "readdir()" function.
+/// Retrieve the list of files/directories of "path" into "gap".
+///
+/// @return OK for success, FAIL for failure.
+int readdir_core(garray_T *gap, const char *path, void *context, CheckItem checkitem)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ ga_init(gap, (int)sizeof(char *), 20);
+
+ Directory dir;
+ if (!os_scandir(&dir, path)) {
+ smsg(_(e_notopen), path);
+ return FAIL;
+ }
+
+ for (;;) {
+ const char *p = os_scandir_next(&dir);
+ if (p == NULL) {
+ break;
+ }
+
+ bool ignore = (p[0] == '.' && (p[1] == NUL || (p[1] == '.' && p[2] == NUL)));
+ if (!ignore && checkitem != NULL) {
+ varnumber_T r = checkitem(context, p);
+ if (r < 0) {
+ break;
+ }
+ if (r == 0) {
+ ignore = true;
+ }
+ }
+
+ if (!ignore) {
+ ga_grow(gap, 1);
+ ((char **)gap->ga_data)[gap->ga_len++] = xstrdup(p);
+ }
+ }
+
+ os_closedir(&dir);
+
+ if (gap->ga_len > 0) {
+ sort_strings((char_u **)gap->ga_data, gap->ga_len);
+ }
+
+ return OK;
+}
+
/// 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.
+///
+/// @param name The path which should be deleted.
+///
+/// @return 0 for success, -1 if some file was not deleted.
int delete_recursive(const char *name)
+ FUNC_ATTR_NONNULL_ALL
{
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((const char *)files[i]) != 0) {
+ char *exp = xstrdup(name);
+ garray_T ga;
+ if (readdir_core(&ga, exp, NULL, NULL) == OK) {
+ for (int i = 0; i < ga.ga_len; i++) {
+ vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp, ((char_u **)ga.ga_data)[i]);
+ if (delete_recursive((const char *)NameBuff) != 0) {
+ // Remember the failure but continue deleting any further
+ // entries.
result = -1;
}
}
- FreeWild(file_count, files);
+ ga_clear_strings(&ga);
+ if (os_rmdir(exp) != 0) {
+ result = -1;
+ }
} else {
result = -1;
}
-
xfree(exp);
- os_rmdir(name);
} else {
+ // Delete symlink only.
result = os_remove(name) == 0 ? 0 : -1;
}
@@ -5372,8 +5440,8 @@ void vim_deltempdir(void)
}
}
-/// Get the name of temp directory. This directory would be created on the first
-/// call to this function.
+/// @return 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) {
@@ -5407,8 +5475,8 @@ static bool vim_settempdir(char *tempdir)
///
/// @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.
+/// @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.
@@ -5719,10 +5787,9 @@ char_u *file_pat_to_reg_pat(const char_u *pat, const char_u *pat_end, char *allo
}
#if defined(EINTR)
-/*
- * Version of read() that retries when interrupted by EINTR (possibly
- * by a SIGWINCH).
- */
+
+/// Version of read() that retries when interrupted by EINTR (possibly
+/// by a SIGWINCH).
long read_eintr(int fd, void *buf, size_t bufsize)
{
long ret;
@@ -5736,10 +5803,8 @@ long read_eintr(int fd, void *buf, size_t bufsize)
return ret;
}
-/*
- * Version of write() that retries when interrupted by EINTR (possibly
- * by a SIGWINCH).
- */
+/// Version of write() that retries when interrupted by EINTR (possibly
+/// by a SIGWINCH).
long write_eintr(int fd, void *buf, size_t bufsize)
{
long ret = 0;