/* vi:set ts=8 sts=4 sw=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
*/
#if defined(MSDOS) || defined(WIN16) || defined(WIN32) || defined(_WIN64)
# include "vimio.h" /* for lseek(), must be before vim.h */
#endif
#if defined __EMX__
# include "vimio.h" /* for mktemp(), CJW 1997-12-03 */
#endif
#include "vim.h"
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#ifdef __TANDEM
# include <limits.h> /* for SSIZE_MAX */
#endif
#if defined(HAVE_UTIME) && defined(HAVE_UTIME_H)
# include <utime.h> /* for struct utimbuf */
#endif
#define BUFSIZE 8192 /* size of normal write buffer */
#define SMBUFSIZE 256 /* size of emergency write buffer */
#ifdef FEAT_CRYPT
# define CRYPT_MAGIC "VimCrypt~01!" /* "01" is the version nr */
# define CRYPT_MAGIC_LEN 12 /* must be multiple of 4! */
#endif
/* Is there any system that doesn't have access()? */
#define USE_MCH_ACCESS
#ifdef FEAT_MBYTE
static char_u *next_fenc __ARGS((char_u **pp));
# ifdef FEAT_EVAL
static char_u *readfile_charconvert __ARGS((char_u *fname, char_u *fenc, int *fdp));
# endif
#endif
#ifdef FEAT_VIMINFO
static void check_marks_read __ARGS((void));
#endif
#ifdef FEAT_CRYPT
static char_u *check_for_cryptkey __ARGS((char_u *cryptkey, char_u *ptr, long *sizep, long *filesizep, int newfile));
#endif
#ifdef UNIX
static void set_file_time __ARGS((char_u *fname, time_t atime, time_t mtime));
#endif
static int set_rw_fname __ARGS((char_u *fname, char_u *sfname));
static int msg_add_fileformat __ARGS((int eol_type));
static void msg_add_eol __ARGS((void));
static int check_mtime __ARGS((buf_T *buf, struct stat *s));
static int time_differs __ARGS((long t1, long t2));
#ifdef FEAT_AUTOCMD
static int apply_autocmds_exarg __ARGS((event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, exarg_T *eap));
static int au_find_group __ARGS((char_u *name));
# define AUGROUP_DEFAULT -1 /* default autocmd group */
# define AUGROUP_ERROR -2 /* errornouse autocmd group */
# define AUGROUP_ALL -3 /* all autocmd groups */
#endif
#if defined(FEAT_CRYPT) || defined(FEAT_MBYTE)
# define HAS_BW_FLAGS
# define FIO_LATIN1 0x01 /* convert Latin1 */
# define FIO_UTF8 0x02 /* convert UTF-8 */
# define FIO_UCS2 0x04 /* convert UCS-2 */
# define FIO_UCS4 0x08 /* convert UCS-4 */
# define FIO_UTF16 0x10 /* convert UTF-16 */
# ifdef WIN3264
# define FIO_CODEPAGE 0x20 /* convert MS-Windows codepage */
# define FIO_PUT_CP(x) (((x) & 0xffff) << 16) /* put codepage in top word */
# define FIO_GET_CP(x) (((x)>>16) & 0xffff) /* get codepage from top word */
# endif
# ifdef MACOS_X
# define FIO_MACROMAN 0x20 /* convert MacRoman */
# endif
# define FIO_ENDIAN_L 0x80 /* little endian */
# define FIO_ENCRYPTED 0x1000 /* encrypt written bytes */
# 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 */
#endif
/* When converting, a read() or write() may leave some bytes to be converted
* for the next call. The value is guessed... */
#define CONV_RESTLEN 30
/* We have to guess how much a sequence of bytes may expand when converting
* with iconv() to be able to allocate a buffer. */
#define ICONV_MULT 8
/*
* Structure to pass arguments from buf_write() to buf_write_bytes().
*/
struct bw_info
{
int bw_fd; /* file descriptor */
char_u *bw_buf; /* buffer with data to be written */
int bw_len; /* lenght of data */
#ifdef HAS_BW_FLAGS
int bw_flags; /* FIO_ flags */
#endif
#ifdef FEAT_MBYTE
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 */
int bw_conv_error; /* set for conversion error */
# ifdef USE_ICONV
iconv_t bw_iconv_fd; /* descriptor for iconv() or -1 */
# endif
#endif
};
static int buf_write_bytes __ARGS((struct bw_info *ip));
#ifdef FEAT_MBYTE
static linenr_T readfile_linenr __ARGS((linenr_T linecnt, char_u *p, char_u *endp));
static int ucs2bytes __ARGS((unsigned c, char_u **pp, int flags));
static int same_encoding __ARGS((char_u *a, char_u *b));
static int get_fio_flags __ARGS((char_u *ptr));
static char_u *check_for_bom __ARGS((char_u *p, long size, int *lenp, int flags));
static int make_bom __ARGS((char_u *buf, char_u *name));
# ifdef WIN3264
static int get_win_fio_flags __ARGS((char_u *ptr));
# endif
# ifdef MACOS_X
static int get_mac_fio_flags __ARGS((char_u *ptr));
# endif
#endif
static int move_lines __ARGS((buf_T *frombuf, buf_T *tobuf));
void
filemess(buf, name, s, attr)
buf_T *buf;
char_u *name;
char_u *s;
int attr;
{
int msg_scroll_save;
if (msg_silent != 0)
return;
msg_add_fname(buf, name); /* put file name in IObuff with quotes */
/* If it's extremely long, truncate it. */
if (STRLEN(IObuff) > IOSIZE - 80)
IObuff[IOSIZE - 80] = NUL;
STRCAT(IObuff, s);
/*
* For the first message may have to start a new line.
* For further ones overwrite the previous one, reset msg_scroll before
* calling filemess().
*/
msg_scroll_save = msg_scroll;
if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0)
msg_scroll = FALSE;
if (!msg_scroll) /* wait a bit when overwriting an error msg */
check_for_delay(FALSE);
msg_start();
msg_scroll = msg_scroll_save;
msg_scrolled_ign = TRUE;
/* may truncate the message to avoid a hit-return prompt */
msg_outtrans_attr(msg_may_trunc(FALSE, IObuff), attr);
msg_clr_eos();
out_flush();
msg_scrolled_ign = FALSE;
}
/*
* Read lines from file "fname" into the buffer after line "from".
*
* 1. We allocate blocks with lalloc, as big as possible.
* 2. Each block is filled with characters from the file with a single read().
* 3. The lines are inserted in the buffer with ml_append().
*
* (caller must check that fname != NULL, unless READ_STDIN is used)
*
* "lines_to_skip" is the number of lines that must be skipped
* "lines_to_read" is the number of lines that are appended
* When not recovering lines_to_skip is 0 and lines_to_read MAXLNUM.
*
* flags:
* READ_NEW starting to edit a new buffer
* READ_FILTER reading filter output
* READ_STDIN read from stdin instead of a file
* READ_BUFFER read from curbuf instead of a file (converting after reading
* stdin)
* READ_DUMMY read into a dummy buffer (to check if file contents changed)
*
* return FAIL for failure, OK otherwise
*/
int
readfile(fname, sfname, from, lines_to_skip, lines_to_read, eap, flags)
char_u *fname;
char_u *sfname;
linenr_T from;
linenr_T lines_to_skip;
linenr_T lines_to_read;
exarg_T *eap; /* can be NULL! */
int flags;
{
int fd = 0;
int newfile = (flags & READ_NEW);
int set_options = newfile || (eap != NULL && eap->read_edit);
int check_readonly;
int filtering = (flags & READ_FILTER);
int read_stdin = (flags & READ_STDIN);
int read_buffer = (flags & READ_BUFFER);
linenr_T read_buf_lnum = 1; /* next line to read from curbuf */
colnr_T read_buf_col = 0; /* next char to read from this line */
char_u c;
linenr_T lnum = from;
char_u *ptr = NULL; /* pointer into read buffer */
char_u *buffer = NULL; /* read buffer */
char_u *new_buffer = NULL; /* init to shut up gcc */
char_u *line_start = NULL; /* init to shut up gcc */
int wasempty; /* buffer was empty before reading */
colnr_T len;
long size = 0;
char_u *p;
long filesize = 0;
int skip_read = FALSE;
#ifdef FEAT_CRYPT
char_u *cryptkey = NULL;
#endif
int split = 0; /* number of split lines */
#define UNKNOWN 0x0fffffff /* file size is unknown */
linenr_T linecnt;
int error = FALSE; /* errors encountered */
int ff_error = EOL_UNKNOWN; /* file format with errors */
long linerest = 0; /* remaining chars in line */
#ifdef UNIX
int perm = 0;
int swap_mode = -1; /* protection bits for swap file */
#else
int perm;
#endif
int fileformat = 0; /* end-of-line format */
int keep_fileformat = FALSE;
struct stat st;
int file_readonly;
linenr_T skip_count = 0;
linenr_T read_count = 0;
int msg_save = msg_scroll;
linenr_T read_no_eol_lnum = 0; /* non-zero lnum when last line of
* last read was missing the eol */
int try_mac = (vim_strchr(p_ffs, 'm') != NULL);
int try_dos = (vim_strchr(p_ffs, 'd') != NULL);
int try_unix = (vim_strchr(p_ffs, 'x') != NULL);
int file_rewind = FALSE;
#ifdef FEAT_MBYTE
int can_retry;
linenr_T conv_error = 0; /* line nr with conversion error */
linenr_T illegal_byte = 0; /* line nr with illegal byte */
int keep_dest_enc = FALSE; /* don't retry when char doesn't fit
in destination encoding */
int bad_char_behavior = BAD_REPLACE;
/* BAD_KEEP, BAD_DROP or character to
* replace with */
char_u *tmpname = NULL; /* name of 'charconvert' output file */
int fio_flags = 0;
char_u *fenc; /* fileencoding to use */
int fenc_alloced; /* fenc_next is in allocated memory */
char_u *fenc_next = NULL; /* next item in 'fencs' or NULL */
int advance_fenc = FALSE;
long real_size = 0;
# ifdef USE_ICONV
iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */
# ifdef FEAT_EVAL
int did_iconv = FALSE; /* TRUE when iconv() failed and trying
'charconvert' next */
# endif
# endif
int converted = FALSE; /* TRUE if conversion done */
int 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[] */
#endif
write_no_eol_lnum = 0; /* in case it was set by the previous read */
/*
* If there is no file name yet, use the one for the read file.
* BF_NOTEDITED is set to reflect this.
* Don't do this for a read from a filter.
* Only do this when 'cpoptions' contains the 'f' flag.
*/
if (curbuf->b_ffname == NULL
&& !filtering
&& fname != NULL
&& vim_strchr(p_cpo, CPO_FNAMER) != NULL
&& !(flags & READ_DUMMY))
{
if (set_rw_fname(fname, sfname) == FAIL)
return FAIL;
}
/* After reading a file the cursor line changes but we don't want to
* display the line. */
ex_no_reprint = TRUE;
/* 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)
sfname = fname;
#if defined(UNIX) || defined(__EMX__)
fname = sfname;
#endif
#ifdef FEAT_AUTOCMD
/*
* 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;
/* Set '[ mark to the line above where the lines go (line 1 if zero). */
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
curbuf->b_op_start.col = 0;
if (newfile)
{
if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname,
FALSE, curbuf, eap))
#ifdef FEAT_EVAL
return aborting() ? FAIL : OK;
#else
return OK;
#endif
}
else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname,
FALSE, NULL, eap))
#ifdef FEAT_EVAL
return aborting() ? FAIL : OK;
#else
return OK;
#endif
curbuf->b_op_start = pos;
}
#endif
if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0)
msg_scroll = FALSE; /* overwrite previous file message */
else
msg_scroll = TRUE; /* don't overwrite previous file message */
/*
* If the name ends in a path separator, we can't open it. Check here,
* because reading the file may actually work, but then creating the swap
* file may destroy it! Reported on MS-DOS and Win 95.
* If the name is too long we might crash further on, quit here.
*/
if (fname != NULL && *fname != NUL)
{
p = fname + STRLEN(fname);
if (after_pathsep(fname, p) || STRLEN(fname) >= MAXPATHL)
{
filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0);
msg_end();
msg_scroll = msg_save;
return FAIL;
}
}
#ifdef UNIX
/*
* On Unix it is possible to read a directory, so we have to
* check for it before the mch_open().
*/
if (!read_stdin && !read_buffer)
{
perm = mch_getperm(fname);
if (perm >= 0 && !S_ISREG(perm) /* not a regular file ... */
# ifdef S_ISFIFO
&& !S_ISFIFO(perm) /* ... or fifo */
# endif
# ifdef S_ISSOCK
&& !S_ISSOCK(perm) /* ... or socket */
# endif
)
{
if (S_ISDIR(perm))
filemess(curbuf, fname, (char_u *)_("is a directory"), 0);
else
filemess(curbuf, fname, (char_u *)_("is not a file"), 0);
msg_end();
msg_scroll = msg_save;
return FAIL;
}
# if defined(MSDOS) || defined(MSWIN) || defined(OS2)
/*
* MS-Windows allows opening a device, but we will probably get stuck
* trying to read it.
*/
if (!p_odev && mch_nodetype(fname) == NODE_WRITABLE)
{
filemess(curbuf, fname, (char_u *)_("is a device (disabled with 'opendevice' option"), 0);
msg_end();
msg_scroll = msg_save;
return FAIL;
}
# endif
}
#endif
/* set default 'fileformat' */
if (set_options)
{
if (eap != NULL && eap->force_ff != 0)
set_fileformat(get_fileformat_force(curbuf, eap), OPT_LOCAL);
else if (*p_ffs != NUL)
set_fileformat(default_fileformat(), OPT_LOCAL);
}
/* set or reset 'binary' */
if (eap != NULL && eap->force_bin != 0)
{
int oldval = curbuf->b_p_bin;
curbuf->b_p_bin = (eap->force_bin == FORCE_BIN);
set_options_bin(oldval, curbuf->b_p_bin, OPT_LOCAL);
}
/*
* When opening a new file we take the readonly flag from the file.
* Default is r/w, can be set to r/o below.
* Don't reset it when in readonly mode
* Only set/reset b_p_ro when BF_CHECK_RO is set.
*/
check_readonly = (newfile && (curbuf->b_flags & BF_CHECK_RO));
if (check_readonly && !readonlymode)
curbuf->b_p_ro = FALSE;
if (newfile && !read_stdin && !read_buffer)
{
/* Remember time of file.
* For RISCOS, also remember the filetype.
*/
if (mch_stat((char *)fname, &st) >= 0)
{
buf_store_time(curbuf, &st, fname);
curbuf->b_mtime_read = curbuf->b_mtime;
#if defined(RISCOS) && defined(FEAT_OSFILETYPE)
/* Read the filetype into the buffer local filetype option. */
mch_read_filetype(fname);
#endif
#ifdef UNIX
/*
* Use the protection bits of the original file for the swap file.
* This makes it possible for others to read the name of the
* edited file from the swapfile, but only if they can read the
* edited file.
* Remove the "write" and "execute" bits for group and others
* (they must not write the swapfile).
* Add the "read" and "write" bits for the user, otherwise we may
* not be able to write to the file ourselves.
* Setting the bits is done below, after creating the swap file.
*/
swap_mode = (st.st_mode & 0644) | 0600;
#endif
#ifdef FEAT_CW_EDITOR
/* Get the FSSpec on MacOS
* TODO: Update it properly when the buffer name changes
*/
(void)GetFSSpecFromPath(curbuf->b_ffname, &curbuf->b_FSSpec);
#endif
#ifdef VMS
curbuf->b_fab_rfm = st.st_fab_rfm;
curbuf->b_fab_rat = st.st_fab_rat;
curbuf->b_fab_mrs = st.st_fab_mrs;
#endif
}
else
{
curbuf->b_mtime = 0;
curbuf->b_mtime_read = 0;
curbuf->b_orig_size = 0;
curbuf->b_orig_mode = 0;
}
/* Reset the "new file" flag. It will be set again below when the
* file doesn't exist. */
curbuf->b_flags &= ~(BF_NEW | BF_NEW_W);
}
/*
* for UNIX: check readonly with perm and mch_access()
* for RISCOS: same as Unix, otherwise file gets re-datestamped!
* for MSDOS and Amiga: check readonly by trying to open the file for writing
*/
file_readonly = FALSE;
if (read_stdin)
{
#if defined(MSDOS) || defined(MSWIN) || defined(OS2)
/* Force binary I/O on stdin to avoid CR-LF -> LF conversion. */
setmode(0, O_BINARY);
#endif
}
else if (!read_buffer)
{
#ifdef USE_MCH_ACCESS
if (
# ifdef UNIX
!(perm & 0222) ||
# endif
mch_access((char *)fname, W_OK))
file_readonly = TRUE;
fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0);
#else
if (!newfile
|| readonlymode
|| (fd = mch_open((char *)fname, O_RDWR | O_EXTRA, 0)) < 0)
{
file_readonly = TRUE;
/* try to open ro */
fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0);
}
#endif
}
if (fd < 0) /* cannot open at all */
{
#ifndef UNIX
int isdir_f;
#endif
msg_scroll = msg_save;
#ifndef UNIX
/*
* On MSDOS and Amiga we can't open a directory, check here.
*/
isdir_f = (mch_isdir(fname));
perm = mch_getperm(fname); /* check if the file exists */
if (isdir_f)
{
filemess(curbuf, sfname, (char_u *)_("is a directory"), 0);
curbuf->b_p_ro = TRUE; /* must use "w!" now */
}
else
#endif
if (newfile)
{
if (perm < 0)
{
/*
* Set the 'new-file' flag, so that when the file has
* been created by someone else, a ":w" will complain.
*/
curbuf->b_flags |= BF_NEW;
/* Create a swap file now, so that other Vims are warned
* that we are editing this file. Don't do this for a
* "nofile" or "nowrite" buffer type. */
#ifdef FEAT_QUICKFIX
if (!bt_dontwrite(curbuf))
#endif
check_need_swap(newfile);
if (dir_of_file_exists(fname))
filemess(curbuf, sfname, (char_u *)_("[New File]"), 0);
else
filemess(curbuf, sfname,
(char_u *)_("[New DIRECTORY]"), 0);
#ifdef FEAT_VIMINFO
/* Even though this is a new file, it might have been
* edited before and deleted. Get the old marks. */
check_marks_read();
#endif
#ifdef FEAT_MBYTE
if (eap != NULL && eap->force_enc != 0)
{
/* set forced 'fileencoding' */
fenc = enc_canonize(eap->cmd + eap->force_enc);
if (fenc != NULL)
set_string_option_direct((char_u *)"fenc", -1,
fenc, OPT_FREE|OPT_LOCAL, 0);
vim_free(fenc);
}
#endif
#ifdef FEAT_AUTOCMD
apply_autocmds_exarg(EVENT_BUFNEWFILE, sfname, sfname,
FALSE, curbuf, eap);
#endif
/* remember the current fileformat */
save_file_ff(curbuf);
#if defined(FEAT_AUTOCMD) && defined(FEAT_EVAL)
if (aborting()) /* autocmds may abort script processing */
return FAIL;
#endif
return OK; /* a new file is not an error */
}
else
{
filemess(curbuf, sfname, (char_u *)(
# ifdef EFBIG
(errno == EFBIG) ? _("[File too big]") :
# endif
_("[Permission Denied]")), 0);
curbuf->b_p_ro = TRUE; /* must use "w!" now */
}
}
return FAIL;
}
/*
* Only set the 'ro' flag for readonly files the first time they are
* loaded. Help files always get readonly mode
*/
if ((check_readonly && file_readonly) || curbuf->b_help)
curbuf->b_p_ro = TRUE;
if (set_options)
{
curbuf->b_p_eol = TRUE;
curbuf->b_start_eol = TRUE;
#ifdef FEAT_MBYTE
curbuf->b_p_bomb = FALSE;
#endif
}
/* Create a swap file now, so that other Vims are warned that we are
* editing this file.
* Don't do this for a "nofile" or "nowrite" buffer type. */
#ifdef FEAT_QUICKFIX
if (!bt_dontwrite(curbuf))
#endif
{
check_need_swap(newfile);
#ifdef UNIX
/* Set swap file protection bits after creating it. */
if (swap_mode > 0 && curbuf->b_ml.ml_mfp->mf_fname != NULL)
(void)mch_setperm(curbuf->b_ml.ml_mfp->mf_fname, (long)swap_mode);
#endif
}
#if defined(HAS_SWAP_EXISTS_ACTION)
/* If "Quit" selected at ATTENTION dialog, don't load the file */
if (swap_exists_action == SEA_QUIT)
{
if (!read_buffer && !read_stdin)
close(fd);
return FAIL;
}
#endif
++no_wait_return; /* don't wait for return yet */
/*
* Set '[ mark to the line above where the lines go (line 1 if zero).
*/
curbuf->b_op_start.lnum = ((from == 0) ? 1 : from);
curbuf->b_op_start.col = 0;
#ifdef FEAT_AUTOCMD
if (!read_buffer)
{
int m = msg_scroll;
int n = msg_scrolled;
buf_T *old_curbuf = curbuf;
/*
* The file must be closed again, the autocommands may want to change
* the file before reading it.
*/
if (!read_stdin)
close(fd); /* ignore errors */
/*
* The output from the autocommands should not overwrite anything and
* should not be overwritten: Set msg_scroll, restore its value if no
* output was done.
*/
msg_scroll = TRUE;
if (filtering)
apply_autocmds_exarg(EVENT_FILTERREADPRE, NULL, sfname,
FALSE, curbuf, eap);
else if (read_stdin)
apply_autocmds_exarg(EVENT_STDINREADPRE, NULL, sfname,
FALSE, curbuf, eap);
else if (newfile)
apply_autocmds_exarg(EVENT_BUFREADPRE, NULL, sfname,
FALSE, curbuf, eap);
else
apply_autocmds_exarg(EVENT_FILEREADPRE, sfname, sfname,
FALSE, NULL, eap);
if (msg_scrolled == n)
msg_scroll = m;
#ifdef FEAT_EVAL
if (aborting()) /* autocmds may abort script processing */
{
--no_wait_return;
msg_scroll = msg_save;
curbuf->b_p_ro = TRUE; /* must use "w!" now */
return FAIL;
}
#endif
/*
* Don't allow the autocommands to change the current buffer.
* Try to re-open the file.
*/
if (!read_stdin && (curbuf != old_curbuf
|| (fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0)) < 0))
{
--no_wait_return;
msg_scroll = msg_save;
if (fd < 0)
EMSG(_("E200: *ReadPre autocommands made the file unreadable"));
else
EMSG(_("E201: *ReadPre autocommands must not change current buffer"));
curbuf->b_p_ro = TRUE; /* must use "w!" now */
return FAIL;
}
}
#endif /* FEAT_AUTOCMD */
/* Autocommands may add lines to the file, need to check if it is empty */
wasempty = (curbuf->b_ml.ml_flags & ML_EMPTY);
if (!recoverymode && !filtering && !(flags & READ_DUMMY))
{
/*
* Show the user that we are busy reading the input. Sometimes this
* may take a while. When reading from stdin another program may
* still be running, don't move the cursor to the last line, unless
* always using the GUI.
*/
if (read_stdin)
{
#ifndef ALWAYS_USE_GUI
mch_msg(_("Vim: Reading from stdin...\n"));
#endif
#ifdef FEAT_GUI
/* Also write a message in the GUI window, if there is one. */
if (gui.in_use && !gui.dying && !gui.starting)
{
p = (char_u *)_("Reading from stdin...");
gui_write(p, (int)STRLEN(p));
}
#endif
}
else if (!read_buffer)
filemess(curbuf, sfname, (char_u *)"", 0);
}
msg_scroll = FALSE; /* overwrite the file message */
/*
* Set linecnt now, before the "retry" caused by a wrong guess for
* fileformat, and after the autocommands, which may change them.
*/
linecnt = curbuf->b_ml.ml_line_count;
#ifdef FEAT_MBYTE
/* "++bad=" argument. */
if (eap != NULL && eap->bad_char != 0)
{
bad_char_behavior = eap->bad_char;
if (set_options)
curbuf->b_bad_char = eap->bad_char;
}
else
curbuf->b_bad_char = 0;
/*
* Decide which 'encoding' to use or use first.
*/
if (eap != NULL && eap->force_enc != 0)
{
fenc = enc_canonize(eap->cmd + eap->force_enc);
fenc_alloced = TRUE;
keep_dest_enc = TRUE;
}
else if (curbuf->b_p_bin)
{
fenc = (char_u *)""; /* binary: don't convert */
fenc_alloced = FALSE;
}
else if (curbuf->b_help)
{
char_u firstline[80];
int fc;
/* Help files are either utf-8 or latin1. Try utf-8 first, if this
* fails it must be latin1.
* Always do this when 'encoding' is "utf-8". Otherwise only do
* this when needed to avoid [converted] remarks all the time.
* It is needed when the first line contains non-ASCII characters.
* That is only in *.??x files. */
fenc = (char_u *)"latin1";
c = enc_utf8;
if (!c && !read_stdin)
{
fc = fname[STRLEN(fname) - 1];
if (TOLOWER_ASC(fc) == 'x')
{
/* Read the first line (and a bit more). Immediately rewind to
* the start of the file. If the read() fails "len" is -1. */
len = vim_read(fd, firstline, 80);
lseek(fd, (off_t)0L, SEEK_SET);
for (p = firstline; p < firstline + len; ++p)
if (*p >= 0x80)
{
c = TRUE;
break;
}
}
}
if (c)
{
fenc_next = fenc;
fenc = (char_u *)"utf-8";
/* When the file is utf-8 but a character doesn't fit in
* 'encoding' don't retry. In help text editing utf-8 bytes
* doesn't make sense. */
if (!enc_utf8)
keep_dest_enc = TRUE;
}
fenc_alloced = FALSE;
}
else if (*p_fencs == NUL)
{
fenc = curbuf->b_p_fenc; /* use format from buffer */
fenc_alloced = FALSE;
}
else
{
fenc_next = p_fencs; /* try items in 'fileencodings' */
fenc = next_fenc(&fenc_next);
fenc_alloced = TRUE;
}
#endif
/*
* Jump back here to retry reading the file in different ways.
* Reasons to retry:
* - encoding conversion failed: try another one from "fenc_next"
* - BOM detected and fenc was set, need to setup conversion
* - "fileformat" check failed: try another
*
* Variables set for special retry actions:
* "file_rewind" Rewind the file to start reading it again.
* "advance_fenc" Advance "fenc" using "fenc_next".
* "skip_read" Re-use already read bytes (BOM detected).
* "did_iconv" iconv() conversion failed, try 'charconvert'.
* "keep_fileformat" Don't reset "fileformat".
*
* Other status indicators:
* "tmpname" When != NULL did conversion with 'charconvert'.
* Output file has to be deleted afterwards.
* "iconv_fd" When != -1 did conversion with iconv().
*/
retry:
if (file_rewind)
{
if (read_buffer)
{
read_buf_lnum = 1;
read_buf_col = 0;
}
else if (read_stdin || lseek(fd, (off_t)0L, SEEK_SET) != 0)
{
/* Can't rewind the file, give up. */
error = TRUE;
goto failed;
}
/* Delete the previously read lines. */
while (lnum > from)
ml_delete(lnum--, FALSE);
file_rewind = FALSE;
#ifdef FEAT_MBYTE
if (set_options)
curbuf->b_p_bomb = FALSE;
conv_error = 0;
#endif
}
/*
* When retrying with another "fenc" and the first time "fileformat"
* will be reset.
*/
if (keep_fileformat)
keep_fileformat = FALSE;
else
{
if (eap != NULL && eap->force_ff != 0)
fileformat = get_fileformat_force(curbuf, eap);
else if (curbuf->b_p_bin)
fileformat = EOL_UNIX; /* binary: use Unix format */
else if (*p_ffs == NUL)
fileformat = get_fileformat(curbuf);/* use format from buffer */
else
fileformat = EOL_UNKNOWN; /* detect from file */
}
#ifdef FEAT_MBYTE
# ifdef USE_ICONV
if (iconv_fd != (iconv_t)-1)
{
/* aborted conversion with iconv(), close the descriptor */
iconv_close(iconv_fd);
iconv_fd = (iconv_t)-1;
}
# endif
if (advance_fenc)
{
/*
* Try the next entry in 'fileencodings'.
*/
advance_fenc = FALSE;
if (eap != NULL && eap->force_enc != 0)
{
/* Conversion given with "++cc=" wasn't possible, read
* without conversion. */
notconverted = TRUE;
conv_error = 0;
if (fenc_alloced)
vim_free(fenc);
fenc = (char_u *)"";
fenc_alloced = FALSE;
}
else
{
if (fenc_alloced)
vim_free(fenc);
if (fenc_next != NULL)
{
fenc = next_fenc(&fenc_next);
fenc_alloced = (fenc_next != NULL);
}
else
{
fenc = (char_u *)"";
fenc_alloced = FALSE;
}
}
if (tmpname != NULL)
{
mch_remove(tmpname); /* delete converted file */
vim_free(tmpname);
tmpname = NULL;
}
}
/*
* Conversion is required when the encoding of the file is different
* from 'encoding' or 'encoding' is UTF-16, UCS-2 or UCS-4 (requires
* conversion to UTF-8).
*/
fio_flags = 0;
converted = (*fenc != NUL && !same_encoding(p_enc, fenc));
if (converted || enc_unicode != 0)
{
/* "ucs-bom" means we need to check the first bytes of the file
* for a BOM. */
if (STRCMP(fenc, ENC_UCSBOM) == 0)
fio_flags = FIO_UCSBOM;
/*
* Check if UCS-2/4 or Latin1 to UTF-8 conversion needs to be
* done. This is handled below after read(). Prepare the
* fio_flags to avoid having to parse the string each time.
* Also check for Unicode to Latin1 conversion, because iconv()
* appears not to handle this correctly. This works just like
* conversion to UTF-8 except how the resulting character is put in
* the buffer.
*/
else if (enc_utf8 || STRCMP(p_enc, "latin1") == 0)
fio_flags = get_fio_flags(fenc);
# ifdef WIN3264
/*
* Conversion from an MS-Windows codepage to UTF-8 or another codepage
* is handled with MultiByteToWideChar().
*/
if (fio_flags == 0)
fio_flags = get_win_fio_flags(fenc);
# endif
# ifdef MACOS_X
/* Conversion from Apple MacRoman to latin1 or UTF-8 */
if (fio_flags == 0)
fio_flags = get_mac_fio_flags(fenc);
# endif
# ifdef USE_ICONV
/*
* Try using iconv() if we can't convert internally.
*/
if (fio_flags == 0
# ifdef FEAT_EVAL
&& !did_iconv
# endif
)
iconv_fd = (iconv_t)my_iconv_open(
enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc);
# endif
# ifdef FEAT_EVAL
/*
* Use the 'charconvert' expression when conversion is required
* and we can't do it internally or with iconv().
*/
if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL
# ifdef USE_ICONV
&& iconv_fd == (iconv_t)-1
# endif
)
{
# ifdef USE_ICONV
did_iconv = FALSE;
# endif
/* Skip conversion when it's already done (retry for wrong
* "fileformat"). */
if (tmpname == NULL)
{
tmpname = readfile_charconvert(fname, fenc, &fd);
if (tmpname == NULL)
{
/* Conversion failed. Try another one. */
advance_fenc = TRUE;
if (fd < 0)
{
/* Re-opening the original file failed! */
EMSG(_("E202: Conversion made file unreadable!"));
error = TRUE;
goto failed;
}
goto retry;
}
}
}
else
# endif
{
if (fio_flags == 0
# ifdef USE_ICONV
&& iconv_fd == (iconv_t)-1
# endif
)
{
/* Conversion wanted but we can't.
* Try the next conversion in 'fileencodings' */
advance_fenc = TRUE;
goto retry;
}
}
}
/* Set "can_retry" when it's possible to rewind the file and try with
* another "fenc" value. It's FALSE when no other "fenc" to try, reading
* stdin or fixed at a specific encoding. */
can_retry = (*fenc != NUL && !read_stdin && !keep_dest_enc);
#endif
if (!skip_read)
{
linerest = 0;
filesize = 0;
skip_count = lines_to_skip;
read_count = lines_to_read;
#ifdef FEAT_MBYTE
conv_restlen = 0;
#endif
}
while (!error && !got_int)
{
/*
* We allocate as much space for the file as we can get, plus
* space for the old line plus room for one terminating NUL.
* The amount is limited by the fact that read() only can read
* upto max_unsigned characters (and other things).
*/
#if SIZEOF_INT <= 2
if (linerest >= 0x7ff0)
{
++split;
*ptr = NL; /* split line by inserting a NL */
size = 1;
}
else
#endif
{
if (!skip_read)
{
#if SIZEOF_INT > 2
# if defined(SSIZE_MAX) && (SSIZE_MAX < 0x10000L)
size = SSIZE_MAX; /* use max I/O size, 52K */
# else
size = 0x10000L; /* use buffer >= 64K */
# endif
#else
size = 0x7ff0L - linerest; /* limit buffer to 32K */
#endif
for ( ; size >= 10; size = (long)((long_u)size >> 1))
{
if ((new_buffer = lalloc((long_u)(size + linerest + 1),
FALSE)) != NULL)
break;
}
if (new_buffer == NULL)
{
do_outofmem_msg((long_u)(size * 2 + linerest + 1));
error = TRUE;
break;
}
if (linerest) /* copy characters from the previous buffer */
mch_memmove(new_buffer, ptr - linerest, (size_t)linerest);
vim_free(buffer);
buffer = new_buffer;
ptr = buffer + linerest;
line_start = buffer;
#ifdef FEAT_MBYTE
/* May need room to translate into.
* For iconv() we don't really know the required space, use a
* factor ICONV_MULT.
* latin1 to utf-8: 1 byte becomes up to 2 bytes
* utf-16 to utf-8: 2 bytes become up to 3 bytes, 4 bytes
* become up to 4 bytes, size must be multiple of 2
* ucs-2 to utf-8: 2 bytes become up to 3 bytes, size must be
* multiple of 2
* ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be
* multiple of 4 */
real_size = (int)size;
# ifdef USE_ICONV
if (iconv_fd != (iconv_t)-1)
size = size / ICONV_MULT;
else
# endif
if (fio_flags & FIO_LATIN1)
size = size / 2;
else if (fio_flags & (FIO_UCS2 | FIO_UTF16))
size = (size * 2 / 3) & ~1;
else if (fio_flags & FIO_UCS4)
size = (size * 2 / 3) & ~3;
else if (fio_flags == FIO_UCSBOM)
size = size / ICONV_MULT; /* worst case */
# ifdef WIN3264
else if (fio_flags & FIO_CODEPAGE)
size = size / ICONV_MULT; /* also worst case */
# endif
# ifdef MACOS_X
else if (fio_flags & FIO_MACROMAN)
size = size / ICONV_MULT; /* also worst case */
# endif
#endif
#ifdef FEAT_MBYTE
if (conv_restlen > 0)
{
/* Insert unconverted bytes from previous line. */
mch_memmove(ptr, conv_rest, conv_restlen);
ptr += conv_restlen;
size -= conv_restlen;
}
#endif
if (read_buffer)
{
/*
* Read bytes from curbuf. Used for converting text read
* from stdin.
*/
if (read_buf_lnum > from)
size = 0;
else
{
int n, ni;
long tlen;
tlen = 0;
for (;;)
{
p = ml_get(read_buf_lnum) + read_buf_col;
n = (int)STRLEN(p);
if ((int)tlen + n + 1 > size)
{
/* Filled up to "size", append partial line.
* Change NL to NUL to reverse the effect done
* below. */
n = (int)(size - tlen);
for (ni = 0; ni < n; ++ni)
{
if (p[ni] == NL)
ptr[tlen++] = NUL;
else
ptr[tlen++] = p[ni];
}
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];
}
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;
}
}
}
}
}
else
{
/*
* Read bytes from the file.
*/
size = vim_read(fd, ptr, size);
}
if (size <= 0)
{
if (size < 0) /* read error */
error = TRUE;
#ifdef FEAT_MBYTE
else if (conv_restlen > 0)
{
/* Reached end-of-file but some trailing bytes could
* not be converted. Truncated file? */
if (conv_error == 0)
conv_error = linecnt;
if (bad_char_behavior != BAD_DROP)
{
fio_flags = 0; /* don't convert this */
# ifdef USE_ICONV
if (iconv_fd != (iconv_t)-1)
{
iconv_close(iconv_fd);
iconv_fd = (iconv_t)-1;
}
# endif
if (bad_char_behavior == BAD_KEEP)
{
/* Keep the trailing bytes as-is. */
size = conv_restlen;
ptr -= conv_restlen;
}
else
{
/* Replace the trailing bytes with the
* replacement character. */
size = 1;
*--ptr = bad_char_behavior;
}
conv_restlen = 0;
}
}
#endif
}
#ifdef FEAT_CRYPT
/*
* At start of file: Check for magic number of encryption.
*/
if (filesize == 0)
cryptkey = check_for_cryptkey(cryptkey, ptr, &size,
&filesize, newfile);
/*
* Decrypt the read bytes.
*/
if (cryptkey != NULL && size > 0)
for (p = ptr; p < ptr + size; ++p)
ZDECODE(*p);
#endif
}
skip_read = FALSE;
#ifdef FEAT_MBYTE
/*
* At start of file (or after crypt magic number): Check for BOM.
* Also check for a BOM for other Unicode encodings, but not after
* converting with 'charconvert' or when a BOM has already been
* found.
*/
if ((filesize == 0
# ifdef FEAT_CRYPT
|| (filesize == CRYPT_MAGIC_LEN && cryptkey != NULL)
# endif
)
&& (fio_flags == FIO_UCSBOM
|| (!curbuf->b_p_bomb
&& tmpname == NULL
&& (*fenc == 'u' || (*fenc == NUL && enc_utf8)))))
{
char_u *ccname;
int blen;
/* no BOM detection in a short file or in binary mode */
if (size < 2 || curbuf->b_p_bin)
ccname = NULL;
else
ccname = check_for_bom(ptr, size, &blen,
fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags(fenc));
if (ccname != NULL)
{
/* Remove BOM from the text */
filesize += blen;
size -= blen;
mch_memmove(ptr, ptr + blen, (size_t)size);
if (set_options)
curbuf->b_p_bomb = TRUE;
}
if (fio_flags == FIO_UCSBOM)
{
if (ccname == NULL)
{
/* No BOM detected: retry with next encoding. */
advance_fenc = TRUE;
}
else
{
/* BOM detected: set "fenc" and jump back */
if (fenc_alloced)
vim_free(fenc);
fenc = ccname;
fenc_alloced = FALSE;
}
/* retry reading without getting new bytes or rewinding */
skip_read = TRUE;
goto retry;
}
}
#endif
/*
* Break here for a read error or end-of-file.
*/
if (size <= 0)
break;
#ifdef FEAT_MBYTE
/* Include not converted bytes. */
ptr -= conv_restlen;
size += conv_restlen;
conv_restlen = 0;
# ifdef USE_ICONV
if (iconv_fd != (iconv_t)-1)
{
/*
* Attempt conversion of the read bytes to 'encoding' using
* iconv().
*/
const char *fromp;
char *top;
size_t from_size;
size_t to_size;
fromp = (char *)ptr;
from_size = size;
ptr += size;
top = (char *)ptr;
to_size = real_size - size;
/*
* If there is conversion error or not enough room try using
* another conversion. Except for when there is no
* alternative (help files).
*/
while ((iconv(iconv_fd, (void *)&fromp, &from_size,
&top, &to_size)
== (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL)
|| from_size > CONV_RESTLEN)
{
if (can_retry)
goto rewind_retry;
if (conv_error == 0)
conv_error = readfile_linenr(linecnt,
ptr, (char_u *)top);
/* Deal with a bad byte and continue with the next. */
++fromp;
--from_size;
if (bad_char_behavior == BAD_KEEP)
{
*top++ = *(fromp - 1);
--to_size;
}
else if (bad_char_behavior != BAD_DROP)
{
*top++ = bad_char_behavior;
--to_size;
}
}
if (from_size > 0)
{
/* Some remaining characters, keep them for the next
* round. */
mch_memmove(conv_rest, (char_u *)fromp, from_size);
conv_restlen = (int)from_size;
}
/* move the linerest to before the converted characters */
line_start = ptr - linerest;
mch_memmove(line_start, buffer, (size_t)linerest);
size = (long)((char_u *)top - ptr);
}
# endif
# ifdef WIN3264
if (fio_flags & FIO_CODEPAGE)
{
char_u *src, *dst;
WCHAR ucs2buf[3];
int ucs2len;
int codepage = FIO_GET_CP(fio_flags);
int bytelen;
int found_bad;
char replstr[2];
/*
* Conversion from an MS-Windows codepage or UTF-8 to UTF-8 or
* a codepage, using standard MS-Windows functions. This
* requires two steps:
* 1. convert from 'fileencoding' to ucs-2
* 2. convert from ucs-2 to 'encoding'
*
* Because there may be illegal bytes AND an incomplete byte
* sequence at the end, we may have to do the conversion one
* character at a time to get it right.
*/
/* Replacement string for WideCharToMultiByte(). */
if (bad_char_behavior > 0)
replstr[0] = bad_char_behavior;
else
replstr[0] = '?';
replstr[1] = NUL;
/*
* Move the bytes to the end of the buffer, so that we have
* room to put the result at the start.
*/
src = ptr + real_size - size;
mch_memmove(src, ptr, size);
/*
* Do the conversion.
*/
dst = ptr;
size = size;
while (size > 0)
{
found_bad = FALSE;
# ifdef CP_UTF8 /* VC 4.1 doesn't define CP_UTF8 */
if (codepage == CP_UTF8)
{
/* Handle CP_UTF8 input ourselves to be able to handle
* trailing bytes properly.
* Get one UTF-8 character from src. */
bytelen = (int)utf_ptr2len_len(src, size);
if (bytelen > size)
{
/* Only got some bytes of a character. Normally
* it's put in "conv_rest", but if it's too long
* deal with it as if they were illegal bytes. */
if (bytelen <= CONV_RESTLEN)
break;
/* weird overlong byte sequence */
bytelen = size;
found_bad = TRUE;
}
else
{
int u8c = utf_ptr2char(src);
if (u8c > 0xffff || (*src >= 0x80 && bytelen == 1))
found_bad = TRUE;
ucs2buf[0] = u8c;
ucs2len = 1;
}
}
else
# endif
{
/* We don't know how long the byte sequence is, try
* from one to three bytes. */
for (bytelen = 1; bytelen <= size && bytelen <= 3;
++bytelen)
{
ucs2len = MultiByteToWideChar(codepage,
MB_ERR_INVALID_CHARS,
(LPCSTR)src, bytelen,
ucs2buf, 3);
if (ucs2len > 0)
break;
}
if (ucs2len == 0)
{
/* If we have only one byte then it's probably an
* incomplete byte sequence. Otherwise discard
* one byte as a bad character. */
if (size == 1)
break;
found_bad = TRUE;
bytelen = 1;
}
}
if (!found_bad)
{
int i;
/* Convert "ucs2buf[ucs2len]" to 'enc' in "dst". */
if (enc_utf8)
{
/* From UCS-2 to UTF-8. Cannot fail. */
for (i = 0; i < ucs2len; ++i)
dst += utf_char2bytes(ucs2buf[i], dst);
}
else
{
BOOL bad = FALSE;
int dstlen;
/* From UCS-2 to "enc_codepage". If the
* conversion uses the default character "?",
* the data doesn't fit in this encoding. */
dstlen = WideCharToMultiByte(enc_codepage, 0,
(LPCWSTR)ucs2buf, ucs2len,
(LPSTR)dst, (int)(src - dst),
replstr, &bad);
if (bad)
found_bad = TRUE;
else
dst += dstlen;
}
}
if (found_bad)
{
/* Deal with bytes we can't convert. */
if (can_retry)
goto rewind_retry;
if (conv_error == 0)
conv_error = readfile_linenr(linecnt, ptr, dst);
if (bad_char_behavior != BAD_DROP)
{
if (bad_char_behavior == BAD_KEEP)
{
mch_memmove(dst, src, bytelen);
dst += bytelen;
}
else
*dst++ = bad_char_behavior;
}
}
src += bytelen;
size -= bytelen;
}
if (size > 0)
{
/* An incomplete byte sequence remaining. */
mch_memmove(conv_rest, src, size);
conv_restlen = size;
}
/* The new size is equal to how much "dst" was advanced. */
size = (long)(dst - ptr);
}
else
# endif
# ifdef MACOS_CONVERT
if (fio_flags & FIO_MACROMAN)
{
/*
* Conversion from Apple MacRoman char encoding to UTF-8 or
* latin1. This is in os_mac_conv.c.
*/
if (macroman2enc(ptr, &size, real_size) == FAIL)
goto rewind_retry;
}
else
# endif
if (fio_flags != 0)
{
int u8c;
char_u *dest;
char_u *tail = NULL;
/*
* "enc_utf8" set: Convert Unicode or Latin1 to UTF-8.
* "enc_utf8" not set: Convert Unicode to Latin1.
* Go from end to start through the buffer, because the number
* of bytes may increase.
* "dest" points to after where the UTF-8 bytes go, "p" points
* to after the next character to convert.
*/
dest = ptr + real_size;
if (fio_flags == FIO_LATIN1 || fio_flags == FIO_UTF8)
{
p = ptr + size;
if (fio_flags == FIO_UTF8)
{
/* Check for a trailing incomplete UTF-8 sequence */
tail = ptr + size - 1;
while (tail > ptr && (*tail & 0xc0) == 0x80)
--tail;
if (tail + utf_byte2len(*tail) <= ptr + size)
tail = NULL;
else
p = tail;
}
}
else if (fio_flags & (FIO_UCS2 | FIO_UTF16))
{
/* Check for a trailing byte */
p = ptr + (size & ~1);
if (size & 1)
tail = p;
if ((fio_flags & FIO_UTF16) && p > ptr)
{
/* Check for a trailing leading word */
if (fio_flags & FIO_ENDIAN_L)
{
u8c = (*--p << 8);
u8c += *--p;
}
else
{
u8c = *--p;
u8c += (*--p << 8);
}
if (u8c >= 0xd800 && u8c <= 0xdbff)
tail = p;
else
p += 2;
}
}
else /* FIO_UCS4 */
{
/* Check for trailing 1, 2 or 3 bytes */
p = ptr + (size & ~3);
if (size & 3)
tail = p;
}
/* If there is a trailing incomplete sequence move it to
* conv_rest[]. */
if (tail != NULL)
{
conv_restlen = (int)((ptr + size) - tail);
mch_memmove(conv_rest, (char_u *)tail, conv_restlen);
size -= conv_restlen;
}
while (p > ptr)
{
if (fio_flags & FIO_LATIN1)
u8c = *--p;
else if (fio_flags & (FIO_UCS2 | FIO_UTF16))
{
if (fio_flags & FIO_ENDIAN_L)
{
u8c = (*--p << 8);
u8c += *--p;
}
else
{
u8c = *--p;
u8c += (*--p << 8);
}
if ((fio_flags & FIO_UTF16)
&& u8c >= 0xdc00 && u8c <= 0xdfff)
{
int u16c;
if (p == ptr)
{
/* Missing leading word. */
if (can_retry)
goto rewind_retry;
if (conv_error == 0)
conv_error = readfile_linenr(linecnt,
ptr, p);
if (bad_char_behavior == BAD_DROP)
continue;
if (bad_char_behavior != BAD_KEEP)
u8c = bad_char_behavior;
}
/* found second word of double-word, get the first
* word and compute the resulting character */
if (fio_flags & FIO_ENDIAN_L)
{
u16c = (*--p << 8);
u16c += *--p;
}
else
{
u16c = *--p;
u16c += (*--p << 8);
}
u8c = 0x10000 + ((u16c & 0x3ff) << 10)
+ (u8c & 0x3ff);
/* Check if the word is indeed a leading word. */
if (u16c < 0xd800 || u16c > 0xdbff)
{
if (can_retry)
goto rewind_retry;
if (conv_error == 0)
conv_error = readfile_linenr(linecnt,
ptr, p);
if (bad_char_behavior == BAD_DROP)
continue;
if (bad_char_behavior != BAD_KEEP)
u8c = bad_char_behavior;
}
}
}
else if (fio_flags & FIO_UCS4)
{
if (fio_flags & FIO_ENDIAN_L)
{
u8c = (*--p << 24);
u8c += (*--p << 16);
u8c += (*--p << 8);
u8c += *--p;
}
else /* big endian */
{
u8c = *--p;
u8c += (*--p << 8);
u8c += (*--p << 16);
u8c += (*--p << 24);
}
}
else /* UTF-8 */
{
if (*--p < 0x80)
u8c = *p;
else
{
len = utf_head_off(ptr, p);
p -= len;
u8c = utf_ptr2char(p);
if (len == 0)
{
/* Not a valid UTF-8 character, retry with
* another fenc when possible, otherwise just
* report the error. */
if (can_retry)
goto rewind_retry;
if (conv_error == 0)
conv_error = readfile_linenr(linecnt,
ptr, p);
if (bad_char_behavior == BAD_DROP)
continue;
if (bad_char_behavior != BAD_KEEP)
u8c = bad_char_behavior;
}
}
}
if (enc_utf8) /* produce UTF-8 */
{
dest -= utf_char2len(u8c);
(void)utf_char2bytes(u8c, dest);
}
else /* produce Latin1 */
{
--dest;
if (u8c >= 0x100)
{
/* character doesn't fit in latin1, retry with
* another fenc when possible, otherwise just
* report the error. */
if (can_retry)
goto rewind_retry;
if (conv_error == 0)
conv_error = readfile_linenr(linecnt, ptr, p);
if (bad_char_behavior == BAD_DROP)
++dest;
else if (bad_char_behavior == BAD_KEEP)
*dest = u8c;
else if (eap != NULL && eap->bad_char != 0)
*dest = bad_char_behavior;
else
*dest = 0xBF;
}
else
*dest = u8c;
}
}
/* move the linerest to before the converted characters */
line_start = dest - linerest;
mch_memmove(line_start, buffer, (size_t)linerest);
size = (long)((ptr + real_size) - dest);
ptr = dest;
}
else if (enc_utf8 && conv_error == 0 && !curbuf->b_p_bin)
{
/* Reading UTF-8: Check if the bytes are valid UTF-8.
* Need to start before "ptr" when part of the character was
* read in the previous read() call. */
for (p = ptr - utf_head_off(buffer, ptr); ; ++p)
{
int todo = (int)((ptr + size) - p);
int l;
if (todo <= 0)
break;
if (*p >= 0x80)
{
/* A length of 1 means it's an illegal byte. Accept
* an incomplete character at the end though, the next
* read() will get the next bytes, we'll check it
* then. */
l = utf_ptr2len_len(p, todo);
if (l > todo)
{
/* Incomplete byte sequence, the next read()
* should get them and check the bytes. */
p += todo;
break;
}
if (l == 1)
{
/* Illegal byte. If we can try another encoding
* do that. */
if (can_retry)
break;
/* Remember the first linenr with an illegal byte */
if (illegal_byte == 0)
illegal_byte = readfile_linenr(linecnt, ptr, p);
# ifdef USE_ICONV
/* When we did a conversion report an error. */
if (iconv_fd != (iconv_t)-1 && conv_error == 0)
conv_error = readfile_linenr(linecnt, ptr, p);
# endif
/* Drop, keep or replace the bad byte. */
if (bad_char_behavior == BAD_DROP)
{
mch_memmove(p, p+1, todo - 1);
--p;
--size;
}
else if (bad_char_behavior != BAD_KEEP)
*p = bad_char_behavior;
}
p += l - 1;
}
}
if (p < ptr + size)
{
/* Detected a UTF-8 error. */
rewind_retry:
/* Retry reading with another conversion. */
# if defined(FEAT_EVAL) && defined(USE_ICONV)
if (*p_ccv != NUL && iconv_fd != (iconv_t)-1)
/* iconv() failed, try 'charconvert' */
did_iconv = TRUE;
else
# endif
/* use next item from 'fileencodings' */
advance_fenc = TRUE;
file_rewind = TRUE;
goto retry;
}
}
#endif
/* count the number of characters (after conversion!) */
filesize += size;
/*
* when reading the first part of a file: guess EOL type
*/
if (fileformat == EOL_UNKNOWN)
{
/* First try finding a NL, for Dos and Unix */
if (try_dos || try_unix)
{
for (p = ptr; p < ptr + size; ++p)
{
if (*p == NL)
{
if (!try_unix
|| (try_dos && p > ptr && p[-1] == CAR))
fileformat = EOL_DOS;
else
fileformat = EOL_UNIX;
break;
}
}
/* Don't give in to EOL_UNIX if EOL_MAC is more likely */
if (fileformat == EOL_UNIX && try_mac)
{
/* Need to reset the counters when retrying fenc. */
try_mac = 1;
try_unix = 1;
for (; p >= ptr && *p != CAR; p--)
;
if (p >= ptr)
{
for (p = ptr; p < ptr + size; ++p)
{
if (*p == NL)
try_unix++;
else if (*p == CAR)
try_mac++;
}
if (try_mac > try_unix)
fileformat = EOL_MAC;
}
}
}
/* No NL found: may use Mac format */
if (fileformat == EOL_UNKNOWN && try_mac)
fileformat = EOL_MAC;
/* Still nothing found? Use first format in 'ffs' */
if (fileformat == EOL_UNKNOWN)
fileformat = default_fileformat();
/* if editing a new file: may set p_tx and p_ff */
if (set_options)
set_fileformat(fileformat, OPT_LOCAL);
}
}
/*
* This loop is executed once for every character read.
* Keep it fast!
*/
if (fileformat == EOL_MAC)
{
--ptr;
while (++ptr, --size >= 0)
{
/* catch most common case first */
if ((c = *ptr) != NUL && c != CAR && c != NL)
continue;
if (c == NUL)
*ptr = NL; /* NULs are replaced by newlines! */
else if (c == NL)
*ptr = CAR; /* NLs are replaced by CRs! */
else
{
if (skip_count == 0)
{
*ptr = NUL; /* end of line */
len = (colnr_T) (ptr - line_start + 1);
if (ml_append(lnum, line_start, len, newfile) == FAIL)
{
error = TRUE;
break;
}
++lnum;
if (--read_count == 0)
{
error = TRUE; /* break loop */
line_start = ptr; /* nothing left to write */
break;
}
}
else
--skip_count;
line_start = ptr + 1;
}
}
}
else
{
--ptr;
while (++ptr, --size >= 0)
{
if ((c = *ptr) != NUL && c != NL) /* catch most common case */
continue;
if (c == NUL)
*ptr = NL; /* NULs are replaced by newlines! */
else
{
if (skip_count == 0)
{
*ptr = NUL; /* end of line */
len = (colnr_T)(ptr - line_start + 1);
if (fileformat == EOL_DOS)
{
if (ptr[-1] == CAR) /* remove CR */
{
ptr[-1] = NUL;
--len;
}
/*
* Reading in Dos format, but no CR-LF found!
* When 'fileformats' includes "unix", delete all
* the lines read so far and start all over again.
* Otherwise give an error message later.
*/
else if (ff_error != EOL_DOS)
{
if ( try_unix
&& !read_stdin
&& (read_buffer
|| lseek(fd, (off_t)0L, SEEK_SET) == 0))
{
fileformat = EOL_UNIX;
if (set_options)
set_fileformat(EOL_UNIX, OPT_LOCAL);
file_rewind = TRUE;
keep_fileformat = TRUE;
goto retry;
}
ff_error = EOL_DOS;
}
}
if (ml_append(lnum, line_start, len, newfile) == FAIL)
{
error = TRUE;
break;
}
++lnum;
if (--read_count == 0)
{
error = TRUE; /* break loop */
line_start = ptr; /* nothing left to write */
break;
}
}
else
--skip_count;
line_start = ptr + 1;
}
}
}
linerest = (long)(ptr - line_start);
ui_breakcheck();
}
failed:
/* not an error, max. number of lines reached */
if (error && read_count == 0)
error = FALSE;
/*
* If we get EOF in the middle of a line, note the fact and
* complete the line ourselves.
* In Dos format ignore a trailing CTRL-Z, unless 'binary' set.
*/
if (!error
&& !got_int
&& linerest != 0
&& !(!curbuf->b_p_bin
&& fileformat == EOL_DOS
&& *line_start == Ctrl_Z
&& ptr == line_start + 1))
{
/* remember for when writing */
if (set_options)
curbuf->b_p_eol = FALSE;
*ptr = NUL;
if (ml_append(lnum, line_start,
(colnr_T)(ptr - line_start + 1), newfile) == FAIL)
error = TRUE;
else
read_no_eol_lnum = ++lnum;
}
if (set_options)
save_file_ff(curbuf); /* remember the current file format */
#ifdef FEAT_CRYPT
if (cryptkey != curbuf->b_p_key)
vim_free(cryptkey);
#endif
#ifdef FEAT_MBYTE
/* If editing a new file: set 'fenc' for the current buffer.
* Also for ":read ++edit file". */
if (set_options)
set_string_option_direct((char_u *)"fenc", -1, fenc,
OPT_FREE|OPT_LOCAL, 0);
if (fenc_alloced)
vim_free(fenc);
# ifdef USE_ICONV
if (iconv_fd != (iconv_t)-1)
{
iconv_close(iconv_fd);
iconv_fd = (iconv_t)-1;
}
# endif
#endif
if (!read_buffer && !read_stdin)
close(fd); /* errors are ignored */
vim_free(buffer);
#ifdef HAVE_DUP
if (read_stdin)
{
/* Use stderr for stdin, makes shell commands work. */
close(0);
dup(2);
}
#endif
#ifdef FEAT_MBYTE
if (tmpname != NULL)
{
mch_remove(tmpname); /* delete converted file */
vim_free(tmpname);
}
#endif
--no_wait_return; /* may wait for return now */
/*
* In recovery mode everything but autocommands is skipped.
*/
if (!recoverymode)
{
/* need to delete the last line, which comes from the empty buffer */
if (newfile && wasempty && !(curbuf->b_ml.ml_flags & ML_EMPTY))
{
#ifdef FEAT_NETBEANS_INTG
netbeansFireChanges = 0;
#endif
ml_delete(curbuf->b_ml.ml_line_count, FALSE);
#ifdef FEAT_NETBEANS_INTG
netbeansFireChanges = 1;
#endif
--linecnt;
}
linecnt = curbuf->b_ml.ml_line_count - linecnt;
if (filesize == 0)
linecnt = 0;
if (newfile || read_buffer)
{
redraw_curbuf_later(NOT_VALID);
#ifdef FEAT_DIFF
/* After reading the text into the buffer the diff info needs to
* be updated. */
diff_invalidate(curbuf);
#endif
#ifdef FEAT_FOLDING
/* All folds in the window are invalid now. Mark them for update
* before triggering autocommands. */
foldUpdateAll(curwin);
#endif
}
else if (linecnt) /* appended at least one line */
appended_lines_mark(from, linecnt);
#ifndef ALWAYS_USE_GUI
/*
* If we were reading from the same terminal as where messages go,
* the screen will have been messed up.
* Switch on raw mode now and clear the screen.
*/
if (read_stdin)
{
settmode(TMODE_RAW); /* set to raw mode */
starttermcap();
screenclear();
}
#endif
if (got_int)
{
if (!(flags & READ_DUMMY))
{
filemess(curbuf, sfname, (char_u *)_(e_interr), 0);
if (newfile)
curbuf->b_p_ro = TRUE; /* must use "w!" now */
}
msg_scroll = msg_save;
#ifdef FEAT_VIMINFO
check_marks_read();
#endif
return OK; /* an interrupt isn't really an error */
}
if (!filtering && !(flags & READ_DUMMY))
{
msg_add_fname(curbuf, sfname); /* fname in IObuff with quotes */
c = FALSE;
#ifdef UNIX
# ifdef S_ISFIFO
if (S_ISFIFO(perm)) /* fifo or socket */
{
STRCAT(IObuff, _("[fifo/socket]"));
c = TRUE;
}
# else
# ifdef S_IFIFO
if ((perm & S_IFMT) == S_IFIFO) /* fifo */
{
STRCAT(IObuff, _("[fifo]"));
c = TRUE;
}
# endif
# ifdef S_IFSOCK
if ((perm & S_IFMT) == S_IFSOCK) /* or socket */
{
STRCAT(IObuff, _("[socket]"));
c = TRUE;
}
# endif
# endif
#endif
if (curbuf->b_p_ro)
{
STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"));
c = TRUE;
}
if (read_no_eol_lnum)
{
msg_add_eol();
c = TRUE;
}
if (ff_error == EOL_DOS)
{
STRCAT(IObuff, _("[CR missing]"));
c = TRUE;
}
if (ff_error == EOL_MAC)
{
STRCAT(IObuff, _("[NL found]"));
c = TRUE;
}
if (split)
{
STRCAT(IObuff, _("[long lines split]"));
c = TRUE;
}
#ifdef FEAT_MBYTE
if (notconverted)
{
STRCAT(IObuff, _("[NOT converted]"));
c = TRUE;
}
else if (converted)
{
STRCAT(IObuff, _("[converted]"));
c = TRUE;
}
#endif
#ifdef FEAT_CRYPT
if (cryptkey != NULL)
{
STRCAT(IObuff, _("[crypted]"));
c = TRUE;
}
#endif
#ifdef FEAT_MBYTE
if (conv_error != 0)
{
sprintf((char *)IObuff + STRLEN(IObuff),
_("[CONVERSION ERROR in line %ld]"), (long)conv_error);
c = TRUE;
}
else if (illegal_byte > 0)
{
sprintf((char *)IObuff + STRLEN(IObuff),
_("[ILLEGAL BYTE in line %ld]"), (long)illegal_byte);
c = TRUE;
}
else
#endif
if (error)
{
STRCAT(IObuff, _("[READ ERRORS]"));
c = TRUE;
}
if (msg_add_fileformat(fileformat))
c = TRUE;
#ifdef FEAT_CRYPT
if (cryptkey != NULL)
msg_add_lines(c, (long)linecnt, filesize - CRYPT_MAGIC_LEN);
else
#endif
msg_add_lines(c, (long)linecnt, filesize);
vim_free(keep_msg);
keep_msg = NULL;
msg_scrolled_ign = TRUE;
#ifdef ALWAYS_USE_GUI
/* Don't show the message when reading stdin, it would end up in a
* message box (which might be shown when exiting!) */
if (read_stdin || read_buffer)
p = msg_may_trunc(FALSE, IObuff);
else
#endif
p = msg_trunc_attr(IObuff, FALSE, 0);
if (read_stdin || read_buffer || restart_edit != 0
|| (msg_scrolled != 0 && !need_wait_return))
/* Need to repeat the message after redrawing when:
* - When reading from stdin (the screen will be cleared next).
* - When restart_edit is set (otherwise there will be a delay
* before redrawing).
* - When the screen was scrolled but there is no wait-return
* prompt. */
set_keep_msg(p, 0);
msg_scrolled_ign = FALSE;
}
/* with errors writing the file requires ":w!" */
if (newfile && (error
#ifdef FEAT_MBYTE
|| conv_error != 0
|| (illegal_byte > 0 && bad_char_behavior != BAD_KEEP)
#endif
))
curbuf->b_p_ro = TRUE;
u_clearline(); /* cannot use "U" command after adding lines */
/*
* In Ex mode: cursor at last new line.
* Otherwise: cursor at first new line.
*/
if (exmode_active)
curwin->w_cursor.lnum = from + linecnt;
else
curwin->w_cursor.lnum = from + 1;
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;
#ifdef WIN32
/*
* Work around a weird problem: When a file has two links (only
* possible on NTFS) and we write through one link, then stat() it
* throught the other link, the timestamp information may be wrong.
* It's correct again after reading the file, thus reset the timestamp
* here.
*/
if (newfile && !read_stdin && !read_buffer
&& mch_stat((char *)fname, &st) >= 0)
{
buf_store_time(curbuf, &st, fname);
curbuf->b_mtime_read = curbuf->b_mtime;
}
#endif
}
msg_scroll = msg_save;
#ifdef FEAT_VIMINFO
/*
* Get the marks before executing autocommands, so they can be used there.
*/
check_marks_read();
#endif
/*
* Trick: We remember if the last line of the read didn't have
* an eol for when writing it again. This is required for
* ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work.
*/
write_no_eol_lnum = read_no_eol_lnum;
#ifdef FEAT_AUTOCMD
if (!read_stdin && !read_buffer)
{
int m = msg_scroll;
int n = msg_scrolled;
/* Save the fileformat now, otherwise the buffer will be considered
* modified if the format/encoding was automatically detected. */
if (set_options)
save_file_ff(curbuf);
/*
* The output from the autocommands should not overwrite anything and
* should not be overwritten: Set msg_scroll, restore its value if no
* output was done.
*/
msg_scroll = TRUE;
if (filtering)
apply_autocmds_exarg(EVENT_FILTERREADPOST, NULL, sfname,
FALSE, curbuf, eap);
else if (newfile)
apply_autocmds_exarg(EVENT_BUFREADPOST, NULL, sfname,
FALSE, curbuf, eap);
else
apply_autocmds_exarg(EVENT_FILEREADPOST, sfname, sfname,
FALSE, NULL, eap);
if (msg_scrolled == n)
msg_scroll = m;
#ifdef FEAT_EVAL
if (aborting()) /* autocmds may abort script processing */
return FAIL;
#endif
}
#endif
if (recoverymode && error)
return FAIL;
return OK;
}
#ifdef FEAT_MBYTE
/*
* From the current line count and characters read after that, estimate the
* line number where we are now.
* Used for error messages that include a line number.
*/
static linenr_T
readfile_linenr(linecnt, p, endp)
linenr_T linecnt; /* line count before reading more bytes */
char_u *p; /* start of more bytes read */
char_u *endp; /* end of more bytes read */
{
char_u *s;
linenr_T lnum;
lnum = curbuf->b_ml.ml_line_count - linecnt + 1;
for (s = p; s < endp; ++s)
if (*s == '\n')
++lnum;
return lnum;
}
#endif
/*
* Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be
* equal to the buffer "buf". Used for calling readfile().
* Returns OK or FAIL.
*/
int
prep_exarg(eap, buf)
exarg_T *eap;
buf_T *buf;
{
eap->cmd = alloc((unsigned)(STRLEN(buf->b_p_ff)
#ifdef FEAT_MBYTE
+ STRLEN(buf->b_p_fenc)
#endif
+ 15));
if (eap->cmd == NULL)
return FAIL;
#ifdef FEAT_MBYTE
sprintf((char *)eap->cmd, "e ++ff=%s ++enc=%s", buf->b_p_ff, buf->b_p_fenc);
eap->force_enc = 14 + (int)STRLEN(buf->b_p_ff);
eap->bad_char = buf->b_bad_char;
#else
sprintf((char *)eap->cmd, "e ++ff=%s", buf->b_p_ff);
#endif
eap->force_ff = 7;
eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN;
eap->read_edit = FALSE;
eap->forceit = FALSE;
return OK;
}
#ifdef FEAT_MBYTE
/*
* 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.
*/
static char_u *
next_fenc(pp)
char_u **pp;
{
char_u *p;
char_u *r;
if (**pp == NUL)
{
*pp = NULL;
return (char_u *)"";
}
p = vim_strchr(*pp, ',');
if (p == NULL)
{
r = enc_canonize(*pp);
*pp += STRLEN(*pp);
}
else
{
r = vim_strnsave(*pp, (int)(p - *pp));
*pp = p + 1;
if (r != NULL)
{
p = enc_canonize(r);
vim_free(r);
r = p;
}
}
if (r == NULL) /* out of memory */
{
r = (char_u *)"";
*pp = NULL;
}
return r;
}
# ifdef FEAT_EVAL
/*
* Convert a file with the 'charconvert' expression.
* This closes the file which is to be read, converts it and opens the
* resulting file for reading.
* Returns name of the resulting converted file (the caller should delete it
* after reading it).
* Returns NULL if the conversion failed ("*fdp" is not set) .
*/
static char_u *
readfile_charconvert(fname, fenc, fdp)
char_u *fname; /* name of input file */
char_u *fenc; /* converted from */
int *fdp; /* in/out: file descriptor of file */
{
char_u *tmpname;
char_u *errmsg = NULL;
tmpname = vim_tempname('r');
if (tmpname == NULL)
errmsg = (char_u *)_("Can't find temp file for conversion");
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)
errmsg = (char_u *)_("Conversion with 'charconvert' failed");
if (errmsg == NULL && (*fdp = mch_open((char *)tmpname,
O_RDONLY | O_EXTRA, 0)) < 0)
errmsg = (char_u *)_("can't read output of 'charconvert'");
}
if (errmsg != NULL)
{
/* Don't use emsg(), it breaks mappings, the retry with
* another type of conversion might still work. */
MSG(errmsg);
if (tmpname != NULL)
{
mch_remove(tmpname); /* delete converted file */
vim_free(tmpname);
tmpname = NULL;
}
}
/* If the input file is closed, open it (caller should check for error). */
if (*fdp < 0)
*fdp = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0);
return tmpname;
}
# endif
#endif
#ifdef FEAT_VIMINFO
/*
* Read marks for the current buffer from the viminfo file, when we support
* buffer marks and the buffer has a name.
*/
static void
check_marks_read()
{
if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0
&& curbuf->b_ffname != NULL)
read_viminfo(NULL, FALSE, TRUE, FALSE);
/* Always set b_marks_read; needed when 'viminfo' is changed to include
* the ' parameter after opening a buffer. */
curbuf->b_marks_read = TRUE;
}
#endif
#ifdef FEAT_CRYPT
/*
* Check for magic number used for encryption.
* If found, the magic number is removed from ptr[*sizep] and *sizep and
* *filesizep are updated.
* Return the (new) encryption key, NULL for no encryption.
*/
static char_u *
check_for_cryptkey(cryptkey, ptr, sizep, filesizep, newfile)
char_u *cryptkey; /* previous encryption key or NULL */
char_u *ptr; /* pointer to read bytes */
long *sizep; /* length of read bytes */
long *filesizep; /* nr of bytes used from file */
int newfile; /* editing a new buffer */
{
if (*sizep >= CRYPT_MAGIC_LEN
&& STRNCMP(ptr, CRYPT_MAGIC, CRYPT_MAGIC_LEN) == 0)
{
if (cryptkey == NULL)
{
if (*curbuf->b_p_key)
cryptkey = curbuf->b_p_key;
else
{
/* When newfile is TRUE, store the typed key
* in the 'key' option and don't free it. */
cryptkey = get_crypt_key(newfile, FALSE);
/* check if empty key entered */
if (cryptkey != NULL && *cryptkey == NUL)
{
if (cryptkey != curbuf->b_p_key)
vim_free(cryptkey);
cryptkey = NULL;
}
}
}
if (cryptkey != NULL)
{
crypt_init_keys(cryptkey);
/* Remove magic number from the text */
*filesizep += CRYPT_MAGIC_LEN;
*sizep -= CRYPT_MAGIC_LEN;
mch_memmove(ptr, ptr + CRYPT_MAGIC_LEN, (size_t)*sizep);
}
}
/* When starting to edit a new file which does not have
* encryption, clear the 'key' option, except when
* starting up (called with -x argument) */
else if (newfile && *curbuf->b_p_key && !starting)
set_option_value((char_u *)"key", 0L, (char_u *)"", OPT_LOCAL);
return cryptkey;
}
#endif
#ifdef UNIX
static void
set_file_time(fname, atime, mtime)
char_u *fname;
time_t atime; /* access time */
time_t mtime; /* modification time */
{
# if defined(HAVE_UTIME) && defined(HAVE_UTIME_H)
struct utimbuf buf;
buf.actime = atime;
buf.modtime = mtime;
(void)utime((char *)fname, &buf);
# else
# if defined(HAVE_UTIMES)
struct timeval tvp[2];
tvp[0].tv_sec = atime;
tvp[0].tv_usec = 0;
tvp[1].tv_sec = mtime;
tvp[1].tv_usec = 0;
# ifdef NeXT
(void)utimes((char *)fname, tvp);
# else
(void)utimes((char *)fname, (const struct timeval *)&tvp);
# endif
# endif
# endif
}
#endif /* UNIX */
#if defined(VMS) && !defined(MIN)
/* Older DECC compiler for VAX doesn't define MIN() */
# define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/*
* buf_write() - write to file "fname" lines "start" through "end"
*
* We do our own buffering here because fwrite() is so slow.
*
* If "forceit" is true, we don't care for errors when attempting backups.
* In case of an error everything possible is done to restore the original
* file. But when "forceit" is TRUE, we risk loosing it.
*
* When "reset_changed" is TRUE and "append" == FALSE and "start" == 1 and
* "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed.
*
* This function must NOT use NameBuff (because it's called by autowrite()).
*
* return FAIL for failure, OK otherwise
*/
int
buf_write(buf, fname, sfname, start, end, eap, append, forceit,
reset_changed, filtering)
buf_T *buf;
char_u *fname;
char_u *sfname;
linenr_T start, end;
exarg_T *eap; /* for forced 'ff' and 'fenc', can be
NULL! */
int append; /* append to the file */
int forceit;
int reset_changed;
int filtering;
{
int fd;
char_u *backup = NULL;
int backup_copy = FALSE; /* copy the original file? */
int dobackup;
char_u *ffname;
char_u *wfname = NULL; /* name of file to write to */
char_u *s;
char_u *ptr;
char_u c;
int len;
linenr_T lnum;
long nchars;
char_u *errmsg = NULL;
char_u *errnum = NULL;
char_u *buffer;
char_u smallbuf[SMBUFSIZE];
char_u *backup_ext;
int bufsize;
long perm; /* file permissions */
int retval = OK;
int newfile = FALSE; /* TRUE if file doesn't exist yet */
int msg_save = msg_scroll;
int overwriting; /* TRUE if writing over original */
int no_eol = FALSE; /* no end-of-line written */
int device = FALSE; /* writing to a device */
struct stat st_old;
int prev_got_int = got_int;
int file_readonly = FALSE; /* overwritten file is read-only */
static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')";
#if defined(UNIX) || defined(__EMX__XX) /*XXX fix me sometime? */
int made_writable = FALSE; /* 'w' bit has been set */
#endif
/* writing everything */
int whole = (start == 1 && end == buf->b_ml.ml_line_count);
#ifdef FEAT_AUTOCMD
linenr_T old_line_count = buf->b_ml.ml_line_count;
#endif
int attr;
int fileformat;
int write_bin;
struct bw_info write_info; /* info for buf_write_bytes() */
#ifdef FEAT_MBYTE
int converted = FALSE;
int notconverted = FALSE;
char_u *fenc; /* effective 'fileencoding' */
char_u *fenc_tofree = NULL; /* allocated "fenc" */
#endif
#ifdef HAS_BW_FLAGS
int wb_flags = 0;
#endif
#ifdef HAVE_ACL
vim_acl_T acl = NULL; /* ACL copied from original file to
backup or new file */
#endif
if (fname == NULL || *fname == NUL) /* safety check */
return FAIL;
/*
* Disallow writing from .exrc and .vimrc in current directory for
* security reasons.
*/
if (check_secure())
return FAIL;
/* Avoid a crash for a long name. */
if (STRLEN(fname) >= MAXPATHL)
{
EMSG(_(e_longname));
return FAIL;
}
#ifdef FEAT_MBYTE
/* must init bw_conv_buf and bw_iconv_fd before jumping to "fail" */
write_info.bw_conv_buf = NULL;
write_info.bw_conv_error = FALSE;
write_info.bw_restlen = 0;
# ifdef USE_ICONV
write_info.bw_iconv_fd = (iconv_t)-1;
# endif
#endif
/* After writing a file changedtick changes but we don't want to display
* the line. */
ex_no_reprint = TRUE;
/*
* If there is no file name yet, use the one for the written file.
* BF_NOTEDITED is set to reflect this (in case the write fails).
* Don't do this when the write is for a filter command.
* Don't do this when appending.
* Only do this when 'cpoptions' contains the 'F' flag.
*/
if (buf->b_ffname == NULL
&& reset_changed
&& whole
&& buf == curbuf
#ifdef FEAT_QUICKFIX
&& !bt_nofile(buf)
#endif
&& !filtering
&& (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL)
&& vim_strchr(p_cpo, CPO_FNAMEW) != NULL)
{
if (set_rw_fname(fname, sfname) == FAIL)
return FAIL;
buf = curbuf; /* just in case autocmds made "buf" invalid */
}
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 */
#ifdef UNIX
fname = sfname;
#endif
if (buf->b_ffname != NULL && fnamecmp(ffname, buf->b_ffname) == 0)
overwriting = TRUE;
else
overwriting = FALSE;
if (exiting)
settmode(TMODE_COOK); /* when exiting allow typahead now */
++no_wait_return; /* don't wait for return yet */
/*
* Set '[ and '] marks to the lines to be written.
*/
buf->b_op_start.lnum = start;
buf->b_op_start.col = 0;
buf->b_op_end.lnum = end;
buf->b_op_end.col = 0;
#ifdef FEAT_AUTOCMD
{
aco_save_T aco;
int buf_ffname = FALSE;
int buf_sfname = FALSE;
int buf_fname_f = FALSE;
int buf_fname_s = FALSE;
int did_cmd = FALSE;
int nofile_err = FALSE;
int empty_memline = (buf->b_ml.ml_mfp == NULL);
/*
* Apply PRE aucocommands.
* Set curbuf to the buffer to be written.
* Careful: The autocommands may call buf_write() recursively!
*/
if (ffname == buf->b_ffname)
buf_ffname = TRUE;
if (sfname == buf->b_sfname)
buf_sfname = TRUE;
if (fname == buf->b_ffname)
buf_fname_f = TRUE;
if (fname == buf->b_sfname)
buf_fname_s = TRUE;
/* set curwin/curbuf to buf and save a few things */
aucmd_prepbuf(&aco, buf);
if (append)
{
if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD,
sfname, sfname, FALSE, curbuf, eap)))
{
#ifdef FEAT_QUICKFIX
if (overwriting && bt_nofile(curbuf))
nofile_err = TRUE;
else
#endif
apply_autocmds_exarg(EVENT_FILEAPPENDPRE,
sfname, sfname, FALSE, curbuf, eap);
}
}
else if (filtering)
{
apply_autocmds_exarg(EVENT_FILTERWRITEPRE,
NULL, sfname, FALSE, curbuf, eap);
}
else if (reset_changed && whole)
{
if (!(did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD,
sfname, sfname, FALSE, curbuf, eap)))
{
#ifdef FEAT_QUICKFIX
if (overwriting && bt_nofile(curbuf))
nofile_err = TRUE;
else
#endif
apply_autocmds_exarg(EVENT_BUFWRITEPRE,
sfname, sfname, FALSE, curbuf, eap);
}
}
else
{
if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD,
sfname, sfname, FALSE, curbuf, eap)))
{
#ifdef FEAT_QUICKFIX
if (overwriting && bt_nofile(curbuf))
nofile_err = TRUE;
else
#endif
apply_autocmds_exarg(EVENT_FILEWRITEPRE,
sfname, sfname, FALSE, curbuf, eap);
}
}
/* restore curwin/curbuf and a few other things */
aucmd_restbuf(&aco);
/*
* In three situations we return here and don't write the file:
* 1. the autocommands deleted or unloaded the buffer.
* 2. The autocommands abort script processing.
* 3. If one of the "Cmd" autocommands was executed.
*/
if (!buf_valid(buf))
buf = NULL;
if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
|| did_cmd || nofile_err
#ifdef FEAT_EVAL
|| aborting()
#endif
)
{
--no_wait_return;
msg_scroll = msg_save;
if (nofile_err)
EMSG(_("E676: No matching autocommands for acwrite buffer"));
if (nofile_err
#ifdef FEAT_EVAL
|| aborting()
#endif
)
/* An aborting error, interrupt or exception in the
* autocommands. */
return FAIL;
if (did_cmd)
{
if (buf == NULL)
/* The buffer was deleted. We assume it was written
* (can't retry anyway). */
return OK;
if (overwriting)
{
/* Assume the buffer was written, update the timestamp. */
ml_timestamp(buf);
if (append)
buf->b_flags &= ~BF_NEW;
else
buf->b_flags &= ~BF_WRITE_MASK;
}
if (reset_changed && buf->b_changed && !append
&& (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL))
/* Buffer still changed, the autocommands didn't work
* properly. */
return FAIL;
return OK;
}
#ifdef FEAT_EVAL
if (!aborting())
#endif
EMSG(_("E203: Autocommands deleted or unloaded buffer to be written"));
return FAIL;
}
/*
* The autocommands may have changed the number of lines in the file.
* When writing the whole file, adjust the end.
* When writing part of the file, assume that the autocommands only
* changed the number of lines that are to be written (tricky!).
*/
if (buf->b_ml.ml_line_count != old_line_count)
{
if (whole) /* write all */
end = buf->b_ml.ml_line_count;
else if (buf->b_ml.ml_line_count > old_line_count) /* more lines */
end += buf->b_ml.ml_line_count - old_line_count;
else /* less lines */
{
end -= old_line_count - buf->b_ml.ml_line_count;
if (end < start)
{
--no_wait_return;
msg_scroll = msg_save;
EMSG(_("E204: Autocommand changed number of lines in unexpected way"));
return FAIL;
}
}
}
/*
* The autocommands may have changed the name of the buffer, which may
* be kept in fname, ffname and sfname.
*/
if (buf_ffname)
ffname = buf->b_ffname;
if (buf_sfname)
sfname = buf->b_sfname;
if (buf_fname_f)
fname = buf->b_ffname;
if (buf_fname_s)
fname = buf->b_sfname;
}
#endif
#ifdef FEAT_NETBEANS_INTG
if (usingNetbeans && isNetbeansBuffer(buf))
{
if (whole)
{
/*
* b_changed can be 0 after an undo, but we still need to write
* the buffer to NetBeans.
*/
if (buf->b_changed || isNetbeansModified(buf))
{
--no_wait_return; /* may wait for return now */
msg_scroll = msg_save;
netbeans_save_buffer(buf); /* no error checking... */
return retval;
}
else
{
errnum = (char_u *)"E656: ";
errmsg = (char_u *)_("NetBeans dissallows writes of unmodified buffers");
buffer = NULL;
goto fail;
}
}
else
{
errnum = (char_u *)"E657: ";
errmsg = (char_u *)_("Partial writes disallowed for NetBeans buffers");
buffer = NULL;
goto fail;
}
}
#endif
if (shortmess(SHM_OVER) && !exiting)
msg_scroll = FALSE; /* overwrite previous file message */
else
msg_scroll = TRUE; /* don't overwrite previous file message */
if (!filtering)
filemess(buf,
#ifndef UNIX
sfname,
#else
fname,
#endif
(char_u *)"", 0); /* show that we are busy */
msg_scroll = FALSE; /* always overwrite the file message now */
buffer = alloc(BUFSIZE);
if (buffer == NULL) /* can't allocate big buffer, use small
* one (to be able to write when out of
* memory) */
{
buffer = smallbuf;
bufsize = SMBUFSIZE;
}
else
bufsize = BUFSIZE;
/*
* Get information about original file (if there is one).
*/
#if defined(UNIX) && !defined(ARCHIE)
st_old.st_dev = st_old.st_ino = 0;
perm = -1;
if (mch_stat((char *)fname, &st_old) < 0)
newfile = TRUE;
else
{
perm = st_old.st_mode;
if (!S_ISREG(st_old.st_mode)) /* not a file */
{
if (S_ISDIR(st_old.st_mode))
{
errnum = (char_u *)"E502: ";
errmsg = (char_u *)_("is a directory");
goto fail;
}
if (mch_nodetype(fname) != NODE_WRITABLE)
{
errnum = (char_u *)"E503: ";
errmsg = (char_u *)_("is not a file or writable device");
goto fail;
}
/* It's a device of some kind (or a fifo) which we can write to
* but for which we can't make a backup. */
device = TRUE;
newfile = TRUE;
perm = -1;
}
}
#else /* !UNIX */
/*
* Check for a writable device name.
*/
c = mch_nodetype(fname);
if (c == NODE_OTHER)
{
errnum = (char_u *)"E503: ";
errmsg = (char_u *)_("is not a file or writable device");
goto fail;
}
if (c == NODE_WRITABLE)
{
# if defined(MSDOS) || defined(MSWIN) || defined(OS2)
/* MS-Windows allows opening a device, but we will probably get stuck
* trying to write to it. */
if (!p_odev)
{
errnum = (char_u *)"E796: ";
errmsg = (char_u *)_("writing to device disabled with 'opendevice' option");
goto fail;
}
# endif
device = TRUE;
newfile = TRUE;
perm = -1;
}
else
{
perm = mch_getperm(fname);
if (perm < 0)
newfile = TRUE;
else if (mch_isdir(fname))
{
errnum = (char_u *)"E502: ";
errmsg = (char_u *)_("is a directory");
goto fail;
}
if (overwriting)
(void)mch_stat((char *)fname, &st_old);
}
#endif /* !UNIX */
if (!device && !newfile)
{
/*
* Check if the file is really writable (when renaming the file to
* make a backup we won't discover it later).
*/
file_readonly = (
# ifdef USE_MCH_ACCESS
# ifdef UNIX
(perm & 0222) == 0 ||
# endif
mch_access((char *)fname, W_OK)
# else
(fd = mch_open((char *)fname, O_RDWR | O_EXTRA, 0)) < 0
? TRUE : (close(fd), FALSE)
# endif
);
if (!forceit && file_readonly)
{
if (vim_strchr(p_cpo, CPO_FWRITE) != NULL)
{
errnum = (char_u *)"E504: ";
errmsg = (char_u *)_(err_readonly);
}
else
{
errnum = (char_u *)"E505: ";
errmsg = (char_u *)_("is read-only (add ! to override)");
}
goto fail;
}
/*
* Check if the timestamp hasn't changed since reading the file.
*/
if (overwriting)
{
retval = check_mtime(buf, &st_old);
if (retval == FAIL)
goto fail;
}
}
#ifdef HAVE_ACL
/*
* For systems that support ACL: get the ACL from the original file.
*/
if (!newfile)
acl = mch_get_acl(fname);
#endif
/*
* If 'backupskip' is not empty, don't make a backup for some files.
*/
dobackup = (p_wb || p_bk || *p_pm != NUL);
#ifdef FEAT_WILDIGN
if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname))
dobackup = FALSE;
#endif
/*
* Save the value of got_int and reset it. We don't want a previous
* interruption cancel writing, only hitting CTRL-C while writing should
* abort it.
*/
prev_got_int = got_int;
got_int = FALSE;
/* Mark the buffer as 'being saved' to prevent changed buffer warnings */
buf->b_saving = TRUE;
/*
* If we are not appending or filtering, the file exists, and the
* 'writebackup', 'backup' or 'patchmode' option is set, need a backup.
* When 'patchmode' is set also make a backup when appending.
*
* Do not make any backup, if 'writebackup' and 'backup' are both switched
* off. This helps when editing large files on almost-full disks.
*/
if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup)
{
#if defined(UNIX) || defined(WIN32)
struct stat st;
#endif
if ((bkc_flags & BKC_YES) || append) /* "yes" */
backup_copy = TRUE;
#if defined(UNIX) || defined(WIN32)
else if ((bkc_flags & BKC_AUTO)) /* "auto" */
{
int i;
# ifdef UNIX
/*
* Don't rename the file when:
* - it's a hard link
* - it's a symbolic link
* - we don't have write permission in the directory
* - we can't set the owner/group of the new file
*/
if (st_old.st_nlink > 1
|| mch_lstat((char *)fname, &st) < 0
|| st.st_dev != st_old.st_dev
|| st.st_ino != st_old.st_ino
# ifndef HAVE_FCHOWN
|| st.st_uid != st_old.st_uid
|| st.st_gid != st_old.st_gid
# endif
)
backup_copy = TRUE;
else
# else
# ifdef WIN32
/* On NTFS file systems hard links are possible. */
if (mch_is_linked(fname))
backup_copy = TRUE;
else
# endif
# endif
{
/*
* Check if we can create a file and set the owner/group to
* the ones from the original file.
* First find a file name that doesn't exist yet (use some
* arbitrary numbers).
*/
STRCPY(IObuff, fname);
for (i = 4913; ; i += 123)
{
sprintf((char *)gettail(IObuff), "%d", i);
if (mch_lstat((char *)IObuff, &st) < 0)
break;
}
fd = mch_open((char *)IObuff,
O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
if (fd < 0) /* can't write in directory */
backup_copy = TRUE;
else
{
# ifdef UNIX
# ifdef HAVE_FCHOWN
fchown(fd, st_old.st_uid, st_old.st_gid);
# endif
if (mch_stat((char *)IObuff, &st) < 0
|| st.st_uid != st_old.st_uid
|| st.st_gid != st_old.st_gid
|| st.st_mode != perm)
backup_copy = TRUE;
# endif
/* Close the file before removing it, on MS-Windows we
* can't delete an open file. */
close(fd);
mch_remove(IObuff);
}
}
}
# ifdef UNIX
/*
* Break symlinks and/or hardlinks if we've been asked to.
*/
if ((bkc_flags & BKC_BREAKSYMLINK) || (bkc_flags & BKC_BREAKHARDLINK))
{
int lstat_res;
lstat_res = mch_lstat((char *)fname, &st);
/* Symlinks. */
if ((bkc_flags & BKC_BREAKSYMLINK)
&& lstat_res == 0
&& st.st_ino != st_old.st_ino)
backup_copy = FALSE;
/* Hardlinks. */
if ((bkc_flags & BKC_BREAKHARDLINK)
&& st_old.st_nlink > 1
&& (lstat_res != 0 || st.st_ino == st_old.st_ino))
backup_copy = FALSE;
}
#endif
#endif
/* make sure we have a valid backup extension to use */
if (*p_bex == NUL)
{
#ifdef RISCOS
backup_ext = (char_u *)"/bak";
#else
backup_ext = (char_u *)".bak";
#endif
}
else
backup_ext = p_bex;
if (backup_copy
&& (fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0)) >= 0)
{
int bfd;
char_u *copybuf, *wp;
int some_error = FALSE;
struct stat st_new;
char_u *dirp;
char_u *rootname;
#if defined(UNIX) && !defined(SHORT_FNAME)
int did_set_shortname;
#endif
copybuf = alloc(BUFSIZE + 1);
if (copybuf == NULL)
{
some_error = TRUE; /* out of memory */
goto nobackup;
}
/*
* Try to make the backup in each directory in the 'bdir' option.
*
* Unix semantics has it, that we may have a writable file,
* that cannot be recreated with a simple open(..., O_CREAT, ) e.g:
* - the directory is not writable,
* - the file may be a symbolic link,
* - the file may belong to another user/group, etc.
*
* For these reasons, the existing writable file must be truncated
* and reused. Creation of a backup COPY will be attempted.
*/
dirp = p_bdir;
while (*dirp)
{
#ifdef UNIX
st_new.st_ino = 0;
st_new.st_dev = 0;
st_new.st_gid = 0;
#endif
/*
* Isolate one directory name, using an entry in 'bdir'.
*/
(void)copy_option_part(&dirp, copybuf, BUFSIZE, ",");
rootname = get_file_in_dir(fname, copybuf);
if (rootname == NULL)
{
some_error = TRUE; /* out of memory */
goto nobackup;
}
#if defined(UNIX) && !defined(SHORT_FNAME)
did_set_shortname = FALSE;
#endif
/*
* May try twice if 'shortname' not set.
*/
for (;;)
{
/*
* Make backup file name.
*/
backup = buf_modname(
#ifdef SHORT_FNAME
TRUE,
#else
(buf->b_p_sn || buf->b_shortname),
#endif
rootname, backup_ext, FALSE);
if (backup == NULL)
{
vim_free(rootname);
some_error = TRUE; /* out of memory */
goto nobackup;
}
/*
* Check if backup file already exists.
*/
if (mch_stat((char *)backup, &st_new) >= 0)
{
#ifdef UNIX
/*
* Check if backup file is same as original file.
* May happen when modname() gave the same file back.
* E.g. silly link, or file name-length reached.
* If we don't check here, we either ruin the file
* when copying or erase it after writing. jw.
*/
if (st_new.st_dev == st_old.st_dev
&& st_new.st_ino == st_old.st_ino)
{
vim_free(backup);
backup = NULL; /* no backup file to delete */
# ifndef SHORT_FNAME
/*
* may try again with 'shortname' set
*/
if (!(buf->b_shortname || buf->b_p_sn))
{
buf->b_shortname = TRUE;
did_set_shortname = TRUE;
continue;
}
/* setting shortname didn't help */
if (did_set_shortname)
buf->b_shortname = FALSE;
# endif
break;
}
#endif
/*
* If we are not going to keep the backup file, don't
* delete an existing one, try to use another name.
* Change one character, just before the extension.
*/
if (!p_bk)
{
wp = backup + STRLEN(backup) - 1
- STRLEN(backup_ext);
if (wp < backup) /* empty file name ??? */
wp = backup;
*wp = 'z';
while (*wp > 'a'
&& mch_stat((char *)backup, &st_new) >= 0)
--*wp;
/* They all exist??? Must be something wrong. */
if (*wp == 'a')
{
vim_free(backup);
backup = NULL;
}
}
}
break;
}
vim_free(rootname);
/*
* Try to create the backup file
*/
if (backup != NULL)
{
/* remove old backup, if present */
mch_remove(backup);
/* Open with O_EXCL to avoid the file being created while
* we were sleeping (symlink hacker attack?) */
bfd = mch_open((char *)backup,
O_WRONLY|O_CREAT|O_EXTRA|O_EXCL|O_NOFOLLOW,
perm & 0777);
if (bfd < 0)
{
vim_free(backup);
backup = NULL;
}
else
{
/* set file protection same as original file, but
* strip s-bit */
(void)mch_setperm(backup, perm & 0777);
#ifdef UNIX
/*
* Try to set the group of the backup same as the
* original file. If this fails, set the protection
* bits for the group same as the protection bits for
* others.
*/
if (st_new.st_gid != st_old.st_gid
# ifdef HAVE_FCHOWN /* sequent-ptx lacks fchown() */
&& fchown(bfd, (uid_t)-1, st_old.st_gid) != 0
# endif
)
mch_setperm(backup,
(perm & 0707) | ((perm & 07) << 3));
#endif
/*
* copy the file.
*/
write_info.bw_fd = bfd;
write_info.bw_buf = copybuf;
#ifdef HAS_BW_FLAGS
write_info.bw_flags = FIO_NOCONVERT;
#endif
while ((write_info.bw_len = vim_read(fd, copybuf,
BUFSIZE)) > 0)
{
if (buf_write_bytes(&write_info) == FAIL)
{
errmsg = (char_u *)_("E506: Can't write to backup file (add ! to override)");
break;
}
ui_breakcheck();
if (got_int)
{
errmsg = (char_u *)_(e_interr);
break;
}
}
if (close(bfd) < 0 && errmsg == NULL)
errmsg = (char_u *)_("E507: Close error for backup file (add ! to override)");
if (write_info.bw_len < 0)
errmsg = (char_u *)_("E508: Can't read file for backup (add ! to override)");
#ifdef UNIX
set_file_time(backup, st_old.st_atime, st_old.st_mtime);
#endif
#ifdef HAVE_ACL
mch_set_acl(backup, acl);
#endif
break;
}
}
}
nobackup:
close(fd); /* ignore errors for closing read file */
vim_free(copybuf);
if (backup == NULL && errmsg == NULL)
errmsg = (char_u *)_("E509: Cannot create backup file (add ! to override)");
/* ignore errors when forceit is TRUE */
if ((some_error || errmsg != NULL) && !forceit)
{
retval = FAIL;
goto fail;
}
errmsg = NULL;
}
else
{
char_u *dirp;
char_u *p;
char_u *rootname;
/*
* Make a backup by renaming the original file.
*/
/*
* If 'cpoptions' includes the "W" flag, we don't want to
* overwrite a read-only file. But rename may be possible
* anyway, thus we need an extra check here.
*/
if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL)
{
errnum = (char_u *)"E504: ";
errmsg = (char_u *)_(err_readonly);
goto fail;
}
/*
*
* Form the backup file name - change path/fo.o.h to
* path/fo.o.h.bak Try all directories in 'backupdir', first one
* that works is used.
*/
dirp = p_bdir;
while (*dirp)
{
/*
* Isolate one directory name and make the backup file name.
*/
(void)copy_option_part(&dirp, IObuff, IOSIZE, ",");
rootname = get_file_in_dir(fname, IObuff);
if (rootname == NULL)
backup = NULL;
else
{
backup = buf_modname(
#ifdef SHORT_FNAME
TRUE,
#else
(buf->b_p_sn || buf->b_shortname),
#endif
rootname, backup_ext, FALSE);
vim_free(rootname);
}
if (backup != NULL)
{
/*
* If we are not going to keep the backup file, don't
* delete an existing one, try to use another name.
* Change one character, just before the extension.
*/
if (!p_bk && mch_getperm(backup) >= 0)
{
p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext);
if (p < backup) /* empty file name ??? */
p = backup;
*p = 'z';
while (*p > 'a' && mch_getperm(backup) >= 0)
--*p;
/* They all exist??? Must be something wrong! */
if (*p == 'a')
{
vim_free(backup);
backup = NULL;
}
}
}
if (backup != NULL)
{
/*
* Delete any existing backup and move the current version
* to the backup. For safety, we don't remove the backup
* until the write has finished successfully. And if the
* 'backup' option is set, leave it around.
*/
/*
* If the renaming of the original file to the backup file
* works, quit here.
*/
if (vim_rename(fname, backup) == 0)
break;
vim_free(backup); /* don't do the rename below */
backup = NULL;
}
}
if (backup == NULL && !forceit)
{
errmsg = (char_u *)_("E510: Can't make backup file (add ! to override)");
goto fail;
}
}
}
#if defined(UNIX) && !defined(ARCHIE)
/* When using ":w!" and the file was read-only: make it writable */
if (forceit && perm >= 0 && !(perm & 0200) && st_old.st_uid == getuid()
&& vim_strchr(p_cpo, CPO_FWRITE) == NULL)
{
perm |= 0200;
(void)mch_setperm(fname, perm);
made_writable = TRUE;
}
#endif
/* When using ":w!" and writing to the current file, 'readonly' makes no
* sense, reset it, unless 'Z' appears in 'cpoptions'. */
if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL)
{
buf->b_p_ro = FALSE;
#ifdef FEAT_TITLE
need_maketitle = TRUE; /* set window title later */
#endif
#ifdef FEAT_WINDOWS
status_redraw_all(); /* redraw status lines later */
#endif
}
if (end > buf->b_ml.ml_line_count)
end = buf->b_ml.ml_line_count;
if (buf->b_ml.ml_flags & ML_EMPTY)
start = end + 1;
/*
* If the original file is being overwritten, there is a small chance that
* we crash in the middle of writing. Therefore the file is preserved now.
* This makes all block numbers positive so that recovery does not need
* the original file.
* Don't do this if there is a backup file and we are exiting.
*/
if (reset_changed && !newfile && overwriting
&& !(exiting && backup != NULL))
{
ml_preserve(buf, FALSE);
if (got_int)
{
errmsg = (char_u *)_(e_interr);
goto restore_backup;
}
}
#ifdef MACOS_CLASSIC /* TODO: Is it need for MACOS_X? (Dany) */
/*
* Before risking to lose the original file verify if there's
* a resource fork to preserve, and if cannot be done warn
* the users. This happens when overwriting without backups.
*/
if (backup == NULL && overwriting && !append)
if (mch_has_resource_fork(fname))
{
errmsg = (char_u *)_("E460: The resource fork would be lost (add ! to override)");
goto restore_backup;
}
#endif
#ifdef VMS
vms_remove_version(fname); /* remove version */
#endif
/* Default: write the the file directly. May write to a temp file for
* multi-byte conversion. */
wfname = fname;
#ifdef FEAT_MBYTE
/* Check for forced 'fileencoding' from "++opt=val" argument. */
if (eap != NULL && eap->force_enc != 0)
{
fenc = eap->cmd + eap->force_enc;
fenc = enc_canonize(fenc);
fenc_tofree = fenc;
}
else
fenc = buf->b_p_fenc;
/*
* The file needs to be converted when 'fileencoding' is set and
* 'fileencoding' differs from 'encoding'.
*/
converted = (*fenc != NUL && !same_encoding(p_enc, fenc));
/*
* Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or
* Latin1 to Unicode conversion. This is handled in buf_write_bytes().
* Prepare the flags for it and allocate bw_conv_buf when needed.
*/
if (converted && (enc_utf8 || STRCMP(p_enc, "latin1") == 0))
{
wb_flags = get_fio_flags(fenc);
if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8))
{
/* Need to allocate a buffer to translate into. */
if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8))
write_info.bw_conv_buflen = bufsize * 2;
else /* FIO_UCS4 */
write_info.bw_conv_buflen = bufsize * 4;
write_info.bw_conv_buf
= lalloc((long_u)write_info.bw_conv_buflen, TRUE);
if (write_info.bw_conv_buf == NULL)
end = 0;
}
}
# ifdef WIN3264
if (converted && wb_flags == 0 && (wb_flags = get_win_fio_flags(fenc)) != 0)
{
/* Convert UTF-8 -> UCS-2 and UCS-2 -> DBCS. Worst-case * 4: */
write_info.bw_conv_buflen = bufsize * 4;
write_info.bw_conv_buf
= lalloc((long_u)write_info.bw_conv_buflen, TRUE);
if (write_info.bw_conv_buf == NULL)
end = 0;
}
# endif
# ifdef MACOS_X
if (converted && wb_flags == 0 && (wb_flags = get_mac_fio_flags(fenc)) != 0)
{
write_info.bw_conv_buflen = bufsize * 3;
write_info.bw_conv_buf
= lalloc((long_u)write_info.bw_conv_buflen, TRUE);
if (write_info.bw_conv_buf == NULL)
end = 0;
}
# endif
# if defined(FEAT_EVAL) || defined(USE_ICONV)
if (converted && wb_flags == 0)
{
# ifdef USE_ICONV
/*
* Use iconv() conversion when conversion is needed and it's not done
* internally.
*/
write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc,
enc_utf8 ? (char_u *)"utf-8" : p_enc);
if (write_info.bw_iconv_fd != (iconv_t)-1)
{
/* We're going to use iconv(), allocate a buffer to convert in. */
write_info.bw_conv_buflen = bufsize * ICONV_MULT;
write_info.bw_conv_buf
= lalloc((long_u)write_info.bw_conv_buflen, TRUE);
if (write_info.bw_conv_buf == NULL)
end = 0;
write_info.bw_first = TRUE;
}
# ifdef FEAT_EVAL
else
# endif
# endif
# ifdef FEAT_EVAL
/*
* When the file needs to be converted with 'charconvert' after
* writing, write to a temp file instead and let the conversion
* overwrite the original file.
*/
if (*p_ccv != NUL)
{
wfname = vim_tempname('w');
if (wfname == NULL) /* Can't write without a tempfile! */
{
errmsg = (char_u *)_("E214: Can't find temp file for writing");
goto restore_backup;
}
}
# endif
}
# endif
if (converted && wb_flags == 0
# ifdef USE_ICONV
&& write_info.bw_iconv_fd == (iconv_t)-1
# endif
# ifdef FEAT_EVAL
&& wfname == fname
# endif
)
{
if (!forceit)
{
errmsg = (char_u *)_("E213: Cannot convert (add ! to write without conversion)");
goto restore_backup;
}
notconverted = TRUE;
}
#endif
/*
* Open the file "wfname" for writing.
* We may try to open the file twice: If we can't write to the
* file and forceit is TRUE we delete the existing file and try to create
* a new one. If this still fails we may have lost the original file!
* (this may happen when the user reached his quotum for number of files).
* Appending will fail if the file does not exist and forceit is FALSE.
*/
while ((fd = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append
? (forceit ? (O_APPEND | O_CREAT) : O_APPEND)
: (O_CREAT | O_TRUNC))
, perm < 0 ? 0666 : (perm & 0777))) < 0)
{
/*
* A forced write will try to create a new file if the old one is
* still readonly. This may also happen when the directory is
* read-only. In that case the mch_remove() will fail.
*/
if (errmsg == NULL)
{
#ifdef UNIX
struct stat st;
/* Don't delete the file when it's a hard or symbolic link. */
if ((!newfile && st_old.st_nlink > 1)
|| (mch_lstat((char *)fname, &st) == 0
&& (st.st_dev != st_old.st_dev
|| st.st_ino != st_old.st_ino)))
errmsg = (char_u *)_("E166: Can't open linked file for writing");
else
#endif
{
errmsg = (char_u *)_("E212: Can't open file for writing");
if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL
&& perm >= 0)
{
#ifdef UNIX
/* we write to the file, thus it should be marked
writable after all */
if (!(perm & 0200))
made_writable = TRUE;
perm |= 0200;
if (st_old.st_uid != getuid() || st_old.st_gid != getgid())
perm &= 0777;
#endif
if (!append) /* don't remove when appending */
mch_remove(wfname);
continue;
}
}
}
restore_backup:
{
struct stat st;
/*
* If we failed to open the file, we don't need a backup. Throw it
* away. If we moved or removed the original file try to put the
* backup in its place.
*/
if (backup != NULL && wfname == fname)
{
if (backup_copy)
{
/*
* There is a small chance that we removed the original,
* try to move the copy in its place.
* This may not work if the vim_rename() fails.
* In that case we leave the copy around.
*/
/* If file does not exist, put the copy in its place */
if (mch_stat((char *)fname, &st) < 0)
vim_rename(backup, fname);
/* if original file does exist throw away the copy */
if (mch_stat((char *)fname, &st) >= 0)
mch_remove(backup);
}
else
{
/* try to put the original file back */
vim_rename(backup, fname);
}
}
/* if original file no longer exists give an extra warning */
if (!newfile && mch_stat((char *)fname, &st) < 0)
end = 0;
}
#ifdef FEAT_MBYTE
if (wfname != fname)
vim_free(wfname);
#endif
goto fail;
}
errmsg = NULL;
#if defined(MACOS_CLASSIC) || defined(WIN3264)
/* TODO: Is it need for MACOS_X? (Dany) */
/*
* On macintosh copy the original files attributes (i.e. the backup)
* This is done in order to preserve the resource fork and the
* Finder attribute (label, comments, custom icons, file creator)
*/
if (backup != NULL && overwriting && !append)
{
if (backup_copy)
(void)mch_copy_file_attribute(wfname, backup);
else
(void)mch_copy_file_attribute(backup, wfname);
}
if (!overwriting && !append)
{
if (buf->b_ffname != NULL)
(void)mch_copy_file_attribute(buf->b_ffname, wfname);
/* Should copy resource fork */
}
#endif
write_info.bw_fd = fd;
#ifdef FEAT_CRYPT
if (*buf->b_p_key && !filtering)
{
crypt_init_keys(buf->b_p_key);
/* Write magic number, so that Vim knows that this file is encrypted
* when reading it again. This also undergoes utf-8 to ucs-2/4
* conversion when needed. */
write_info.bw_buf = (char_u *)CRYPT_MAGIC;
write_info.bw_len = CRYPT_MAGIC_LEN;
write_info.bw_flags = FIO_NOCONVERT;
if (buf_write_bytes(&write_info) == FAIL)
end = 0;
wb_flags |= FIO_ENCRYPTED;
}
#endif
write_info.bw_buf = buffer;
nchars = 0;
/* use "++bin", "++nobin" or 'binary' */
if (eap != NULL && eap->force_bin != 0)
write_bin = (eap->force_bin == FORCE_BIN);
else
write_bin = buf->b_p_bin;
#ifdef FEAT_MBYTE
/*
* The BOM is written just after the encryption magic number.
* Skip it when appending and the file already existed, the BOM only makes
* sense at the start of the file.
*/
if (buf->b_p_bomb && !write_bin && (!append || perm < 0))
{
write_info.bw_len = make_bom(buffer, fenc);
if (write_info.bw_len > 0)
{
/* don't convert, do encryption */
write_info.bw_flags = FIO_NOCONVERT | wb_flags;
if (buf_write_bytes(&write_info) == FAIL)
end = 0;
else
nchars += write_info.bw_len;
}
}
#endif
write_info.bw_len = bufsize;
#ifdef HAS_BW_FLAGS
write_info.bw_flags = wb_flags;
#endif
fileformat = get_fileformat_force(buf, eap);
s = buffer;
len = 0;
for (lnum = start; lnum <= end; ++lnum)
{
/*
* The next while loop is done once for each character written.
* Keep it fast!
*/
ptr = ml_get_buf(buf, lnum, FALSE) - 1;
while ((c = *++ptr) != NUL)
{
if (c == NL)
*s = NUL; /* replace newlines with NULs */
else if (c == CAR && fileformat == EOL_MAC)
*s = NL; /* Mac: replace CRs with NLs */
else
*s = c;
++s;
if (++len != bufsize)
continue;
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0; /* write error: break loop */
break;
}
nchars += bufsize;
s = buffer;
len = 0;
}
/* write failed or last line has no EOL: stop here */
if (end == 0
|| (lnum == end
&& write_bin
&& (lnum == write_no_eol_lnum
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol))))
{
++lnum; /* written the line, count it */
no_eol = TRUE;
break;
}
if (fileformat == EOL_UNIX)
*s++ = NL;
else
{
*s++ = CAR; /* EOL_MAC or EOL_DOS: write CR */
if (fileformat == EOL_DOS) /* write CR-NL */
{
if (++len == bufsize)
{
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0; /* write error: break loop */
break;
}
nchars += bufsize;
s = buffer;
len = 0;
}
*s++ = NL;
}
}
if (++len == bufsize && end)
{
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0; /* write error: break loop */
break;
}
nchars += bufsize;
s = buffer;
len = 0;
ui_breakcheck();
if (got_int)
{
end = 0; /* Interrupted, break loop */
break;
}
}
#ifdef VMS
/*
* On VMS there is a problem: newlines get added when writing blocks
* at a time. Fix it by writing a line at a time.
* This is much slower!
* Explanation: VAX/DECC RTL insists that records in some RMS
* structures end with a newline (carriage return) character, and if
* they don't it adds one.
* With other RMS structures it works perfect without this fix.
*/
if ((buf->b_fab_rat & (FAB$M_FTN | FAB$M_CR)) != 0)
{
int b2write;
buf->b_fab_mrs = (buf->b_fab_mrs == 0
? MIN(4096, bufsize)
: MIN(buf->b_fab_mrs, bufsize));
b2write = len;
while (b2write > 0)
{
write_info.bw_len = MIN(b2write, buf->b_fab_mrs);
if (buf_write_bytes(&write_info) == FAIL)
{
end = 0;
break;
}
b2write -= MIN(b2write, buf->b_fab_mrs);
}
write_info.bw_len = bufsize;
nchars += len;
s = buffer;
len = 0;
}
#endif
}
if (len > 0 && end > 0)
{
write_info.bw_len = len;
if (buf_write_bytes(&write_info) == FAIL)
end = 0; /* write error */
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)
{
errmsg = (char_u *)_("E667: Fsync failed");
end = 0;
}
#endif
#ifdef UNIX
/* When creating a new file, set its owner/group to that of the original
* file. Get the new device and inode number. */
if (backup != NULL && !backup_copy)
{
# ifdef HAVE_FCHOWN
struct stat st;
/* don't change the owner when it's already OK, some systems remove
* permission or ACL stuff */
if (mch_stat((char *)wfname, &st) < 0
|| st.st_uid != st_old.st_uid
|| st.st_gid != st_old.st_gid)
{
fchown(fd, st_old.st_uid, st_old.st_gid);
if (perm >= 0) /* set permission again, may have changed */
(void)mch_setperm(wfname, perm);
}
# endif
buf_setino(buf);
}
else if (buf->b_dev < 0)
/* Set the inode when creating a new file. */
buf_setino(buf);
#endif
if (close(fd) != 0)
{
errmsg = (char_u *)_("E512: Close failed");
end = 0;
}
#ifdef UNIX
if (made_writable)
perm &= ~0200; /* reset 'w' bit for security reasons */
#endif
if (perm >= 0) /* set perm. of new file same as old file */
(void)mch_setperm(wfname, perm);
#ifdef RISCOS
if (!append && !filtering)
/* Set the filetype after writing the file. */
mch_set_filetype(wfname, buf->b_p_oft);
#endif
#ifdef HAVE_ACL
/* Probably need to set the ACL before changing the user (can't set the
* ACL on a file the user doesn't own). */
if (!backup_copy)
mch_set_acl(wfname, acl);
#endif
#if defined(FEAT_MBYTE) && defined(FEAT_EVAL)
if (wfname != fname)
{
/*
* The file was written to a temp file, now it needs to be converted
* 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;
end = 0;
}
}
mch_remove(wfname);
vim_free(wfname);
}
#endif
if (end == 0)
{
if (errmsg == NULL)
{
#ifdef FEAT_MBYTE
if (write_info.bw_conv_error)
errmsg = (char_u *)_("E513: write error, conversion failed (make 'fenc' empty to override)");
else
#endif
if (got_int)
errmsg = (char_u *)_(e_interr);
else
errmsg = (char_u *)_("E514: write error (file system full?)");
}
/*
* If we have a backup file, try to put it in place of the new file,
* because the new file is probably corrupt. This avoids loosing the
* original file when trying to make a backup when writing the file a
* second time.
* When "backup_copy" is set we need to copy the backup over the new
* file. Otherwise rename the backup file.
* If this is OK, don't give the extra warning message.
*/
if (backup != NULL)
{
if (backup_copy)
{
/* This may take a while, if we were interrupted let the user
* know we got the message. */
if (got_int)
{
MSG(_(e_interr));
out_flush();
}
if ((fd = mch_open((char *)backup, O_RDONLY | O_EXTRA, 0)) >= 0)
{
if ((write_info.bw_fd = mch_open((char *)fname,
O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA,
perm & 0777)) >= 0)
{
/* copy the file. */
write_info.bw_buf = smallbuf;
#ifdef HAS_BW_FLAGS
write_info.bw_flags = FIO_NOCONVERT;
#endif
while ((write_info.bw_len = vim_read(fd, smallbuf,
SMBUFSIZE)) > 0)
if (buf_write_bytes(&write_info) == FAIL)
break;
if (close(write_info.bw_fd) >= 0
&& write_info.bw_len == 0)
end = 1; /* success */
}
close(fd); /* ignore errors for closing read file */
}
}
else
{
if (vim_rename(backup, fname) == 0)
end = 1;
}
}
goto fail;
}
lnum -= start; /* compute number of written lines */
--no_wait_return; /* may wait for return now */
#if !(defined(UNIX) || defined(VMS))
fname = sfname; /* use shortname now, for the messages */
#endif
if (!filtering)
{
msg_add_fname(buf, fname); /* put fname in IObuff with quotes */
c = FALSE;
#ifdef FEAT_MBYTE
if (write_info.bw_conv_error)
{
STRCAT(IObuff, _(" CONVERSION ERROR"));
c = TRUE;
}
else if (notconverted)
{
STRCAT(IObuff, _("[NOT converted]"));
c = TRUE;
}
else if (converted)
{
STRCAT(IObuff, _("[converted]"));
c = TRUE;
}
#endif
if (device)
{
STRCAT(IObuff, _("[Device]"));
c = TRUE;
}
else if (newfile)
{
STRCAT(IObuff, shortmess(SHM_NEW) ? _("[New]") : _("[New File]"));
c = TRUE;
}
if (no_eol)
{
msg_add_eol();
c = TRUE;
}
/* may add [unix/dos/mac] */
if (msg_add_fileformat(fileformat))
c = TRUE;
#ifdef FEAT_CRYPT
if (wb_flags & FIO_ENCRYPTED)
{
STRCAT(IObuff, _("[crypted]"));
c = TRUE;
}
#endif
msg_add_lines(c, (long)lnum, nchars); /* add line/char count */
if (!shortmess(SHM_WRITE))
{
if (append)
STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"));
else
STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"));
}
set_keep_msg(msg_trunc_attr(IObuff, FALSE, 0), 0);
}
/* When written everything correctly: reset 'modified'. Unless not
* writing to the original file and '+' is not in 'cpoptions'. */
if (reset_changed && whole && !append
#ifdef FEAT_MBYTE
&& !write_info.bw_conv_error
#endif
&& (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)
)
{
unchanged(buf, TRUE);
u_unchanged(buf);
}
/*
* If written to the current file, update the timestamp of the swap file
* and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime.
*/
if (overwriting)
{
ml_timestamp(buf);
if (append)
buf->b_flags &= ~BF_NEW;
else
buf->b_flags &= ~BF_WRITE_MASK;
}
/*
* If we kept a backup until now, and we are in patch mode, then we make
* the backup file our 'original' file.
*/
if (*p_pm && dobackup)
{
char *org = (char *)buf_modname(
#ifdef SHORT_FNAME
TRUE,
#else
(buf->b_p_sn || buf->b_shortname),
#endif
fname, p_pm, FALSE);
if (backup != NULL)
{
struct stat st;
/*
* If the original file does not exist yet
* the current backup file becomes the original file
*/
if (org == NULL)
EMSG(_("E205: Patchmode: can't save original file"));
else if (mch_stat(org, &st) < 0)
{
vim_rename(backup, (char_u *)org);
vim_free(backup); /* don't delete the file */
backup = NULL;
#ifdef UNIX
set_file_time((char_u *)org, st_old.st_atime, st_old.st_mtime);
#endif
}
}
/*
* If there is no backup file, remember that a (new) file was
* created.
*/
else
{
int empty_fd;
if (org == NULL
|| (empty_fd = mch_open(org,
O_CREAT | O_EXTRA | O_EXCL | O_NOFOLLOW,
perm < 0 ? 0666 : (perm & 0777))) < 0)
EMSG(_("E206: patchmode: can't touch empty original file"));
else
close(empty_fd);
}
if (org != NULL)
{
mch_setperm((char_u *)org, mch_getperm(fname) & 0777);
vim_free(org);
}
}
/*
* Remove the backup unless 'backup' option is set
*/
if (!p_bk && backup != NULL && mch_remove(backup) != 0)
EMSG(_("E207: Can't delete backup file"));
#ifdef FEAT_SUN_WORKSHOP
if (usingSunWorkShop)
workshop_file_saved((char *) ffname);
#endif
goto nofail;
/*
* Finish up. We get here either after failure or success.
*/
fail:
--no_wait_return; /* may wait for return now */
nofail:
/* Done saving, we accept changed buffer warnings again */
buf->b_saving = FALSE;
vim_free(backup);
if (buffer != smallbuf)
vim_free(buffer);
#ifdef FEAT_MBYTE
vim_free(fenc_tofree);
vim_free(write_info.bw_conv_buf);
# ifdef USE_ICONV
if (write_info.bw_iconv_fd != (iconv_t)-1)
{
iconv_close(write_info.bw_iconv_fd);
write_info.bw_iconv_fd = (iconv_t)-1;
}
# endif
#endif
#ifdef HAVE_ACL
mch_free_acl(acl);
#endif
if (errmsg != NULL)
{
int numlen = errnum != NULL ? (int)STRLEN(errnum) : 0;
attr = hl_attr(HLF_E); /* set highlight for error messages */
msg_add_fname(buf,
#ifndef UNIX
sfname
#else
fname
#endif
); /* put file name in IObuff with quotes */
if (STRLEN(IObuff) + STRLEN(errmsg) + numlen >= IOSIZE)
IObuff[IOSIZE - STRLEN(errmsg) - numlen - 1] = NUL;
/* If the error message has the form "is ...", put the error number in
* front of the file name. */
if (errnum != NULL)
{
mch_memmove(IObuff + numlen, IObuff, STRLEN(IObuff) + 1);
mch_memmove(IObuff, errnum, (size_t)numlen);
}
STRCAT(IObuff, errmsg);
emsg(IObuff);
retval = FAIL;
if (end == 0)
{
MSG_PUTS_ATTR(_("\nWARNING: Original file may be lost or damaged\n"),
attr | MSG_HIST);
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. */
if (mch_stat((char *)fname, &st_old) >= 0)
{
buf_store_time(buf, &st_old, fname);
buf->b_mtime_read = buf->b_mtime;
}
}
}
msg_scroll = msg_save;
#ifdef FEAT_AUTOCMD
#ifdef FEAT_EVAL
if (!should_abort(retval))
#else
if (!got_int)
#endif
{
aco_save_T aco;
write_no_eol_lnum = 0; /* in case it was set by the previous read */
/*
* Apply POST autocommands.
* Careful: The autocommands may call buf_write() recursively!
*/
aucmd_prepbuf(&aco, buf);
if (append)
apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname,
FALSE, curbuf, eap);
else if (filtering)
apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname,
FALSE, curbuf, eap);
else if (reset_changed && whole)
apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname,
FALSE, curbuf, eap);
else
apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname,
FALSE, curbuf, eap);
/* restore curwin/curbuf and a few other things */
aucmd_restbuf(&aco);
#ifdef FEAT_EVAL
if (aborting()) /* autocmds may abort script processing */
retval = FALSE;
#endif
}
#endif
got_int |= prev_got_int;
#ifdef MACOS_CLASSIC /* TODO: Is it need for MACOS_X? (Dany) */
/* Update machine specific information. */
mch_post_buffer_write(buf);
#endif
return retval;
}
/*
* 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(fname, sfname)
char_u *fname;
char_u *sfname;
{
#ifdef FEAT_AUTOCMD
/* It's like the unnamed buffer is deleted.... */
if (curbuf->b_p_bl)
apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf);
apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, FALSE, curbuf);
# ifdef FEAT_EVAL
if (aborting()) /* autocmds may abort script processing */
return FAIL;
# endif
#endif
if (setfname(curbuf, fname, sfname, FALSE) == OK)
curbuf->b_flags |= BF_NOTEDITED;
#ifdef FEAT_AUTOCMD
/* ....and a new named one is created */
apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf);
if (curbuf->b_p_bl)
apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf);
# ifdef FEAT_EVAL
if (aborting()) /* autocmds may abort script processing */
return FAIL;
# endif
/* Do filetype detection now if 'filetype' is empty. */
if (*curbuf->b_p_ft == NUL)
{
if (au_has_group((char_u *)"filetypedetect"))
(void)do_doautocmd((char_u *)"filetypedetect BufRead", FALSE);
do_modelines(0);
}
#endif
return OK;
}
/*
* Put file name into IObuff with quotes.
*/
void
msg_add_fname(buf, fname)
buf_T *buf;
char_u *fname;
{
if (fname == NULL)
fname = (char_u *)"-stdin-";
home_replace(buf, fname, IObuff + 1, IOSIZE - 4, TRUE);
IObuff[0] = '"';
STRCAT(IObuff, "\" ");
}
/*
* Append message for text mode to IObuff.
* Return TRUE if something appended.
*/
static int
msg_add_fileformat(eol_type)
int eol_type;
{
#ifndef USE_CRNL
if (eol_type == EOL_DOS)
{
STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"));
return TRUE;
}
#endif
#ifndef USE_CR
if (eol_type == EOL_MAC)
{
STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"));
return TRUE;
}
#endif
#if defined(USE_CRNL) || defined(USE_CR)
if (eol_type == EOL_UNIX)
{
STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"));
return TRUE;
}
#endif
return FALSE;
}
/*
* Append line and character count to IObuff.
*/
void
msg_add_lines(insert_space, lnum, nchars)
int insert_space;
long lnum;
long nchars;
{
char_u *p;
p = IObuff + STRLEN(IObuff);
if (insert_space)
*p++ = ' ';
if (shortmess(SHM_LINES))
sprintf((char *)p, "%ldL, %ldC", lnum, nchars);
else
{
if (lnum == 1)
STRCPY(p, _("1 line, "));
else
sprintf((char *)p, _("%ld lines, "), lnum);
p += STRLEN(p);
if (nchars == 1)
STRCPY(p, _("1 character"));
else
sprintf((char *)p, _("%ld characters"), nchars);
}
}
/*
* Append message for missing line separator to IObuff.
*/
static void
msg_add_eol()
{
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.
*/
static int
check_mtime(buf, st)
buf_T *buf;
struct stat *st;
{
if (buf->b_mtime_read != 0
&& time_differs((long)st->st_mtime, buf->b_mtime_read))
{
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 */
MSG_ATTR(_("WARNING: The file has been changed since reading it!!!"),
hl_attr(HLF_E));
if (ask_yesno((char_u *)_("Do you really want to write to it"),
TRUE) == 'n')
return FAIL;
msg_scroll = FALSE; /* always overwrite the file message now */
}
return OK;
}
static int
time_differs(t1, t2)
long t1, t2;
{
#if defined(__linux__) || defined(MSDOS) || 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);
#else
return (t1 != t2);
#endif
}
/*
* Call write() to write a number of bytes to the file.
* Also handles encryption and 'encoding' conversion.
*
* Return FAIL for failure, OK otherwise.
*/
static int
buf_write_bytes(ip)
struct bw_info *ip;
{
int wlen;
char_u *buf = ip->bw_buf; /* data to write */
int len = ip->bw_len; /* length of data */
#ifdef HAS_BW_FLAGS
int flags = ip->bw_flags; /* extra flags */
#endif
#ifdef FEAT_MBYTE
/*
* Skip conversion when writing the crypt magic number or the BOM.
*/
if (!(flags & FIO_NOCONVERT))
{
char_u *p;
unsigned c;
int n;
if (flags & FIO_UTF8)
{
/*
* Convert latin1 in the buffer to UTF-8 in the file.
*/
p = ip->bw_conv_buf; /* translate to buffer */
for (wlen = 0; wlen < len; ++wlen)
p += utf_char2bytes(buf[wlen], p);
buf = ip->bw_conv_buf;
len = (int)(p - ip->bw_conv_buf);
}
else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1))
{
/*
* Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or
* Latin1 chars in the file.
*/
if (flags & FIO_LATIN1)
p = buf; /* translate in-place (can only get shorter) */
else
p = ip->bw_conv_buf; /* translate to buffer */
for (wlen = 0; wlen < len; wlen += n)
{
if (wlen == 0 && ip->bw_restlen != 0)
{
int l;
/* Use remainder of previous call. Append the start of
* buf[] to get a full sequence. Might still be too
* short! */
l = CONV_RESTLEN - ip->bw_restlen;
if (l > len)
l = len;
mch_memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l);
n = utf_ptr2len_len(ip->bw_rest, ip->bw_restlen + l);
if (n > ip->bw_restlen + len)
{
/* We have an incomplete byte sequence at the end to
* be written. We can't convert it without the
* remaining bytes. Keep them for the next call. */
if (ip->bw_restlen + len > CONV_RESTLEN)
return FAIL;
ip->bw_restlen += len;
break;
}
if (n > 1)
c = utf_ptr2char(ip->bw_rest);
else
c = ip->bw_rest[0];
if (n >= ip->bw_restlen)
{
n -= ip->bw_restlen;
ip->bw_restlen = 0;
}
else
{
ip->bw_restlen -= n;
mch_memmove(ip->bw_rest, ip->bw_rest + n,
(size_t)ip->bw_restlen);
n = 0;
}
}
else
{
n = utf_ptr2len_len(buf + wlen, len - wlen);
if (n > len - wlen)
{
/* We have an incomplete byte sequence at the end to
* be written. We can't convert it without the
* remaining bytes. Keep them for the next call. */
if (len - wlen > CONV_RESTLEN)
return FAIL;
ip->bw_restlen = len - wlen;
mch_memmove(ip->bw_rest, buf + wlen,
(size_t)ip->bw_restlen);
break;
}
if (n > 1)
c = utf_ptr2char(buf + wlen);
else
c = buf[wlen];
}
ip->bw_conv_error |= ucs2bytes(c, &p, flags);
}
if (flags & FIO_LATIN1)
len = (int)(p - buf);
else
{
buf = ip->bw_conv_buf;
len = (int)(p - ip->bw_conv_buf);
}
}
# ifdef WIN3264
else if (flags & FIO_CODEPAGE)
{
/*
* Convert UTF-8 or codepage to UCS-2 and then to MS-Windows
* codepage.
*/
char_u *from;
size_t fromlen;
char_u *to;
int u8c;
BOOL bad = FALSE;
int needed;
if (ip->bw_restlen > 0)
{
/* Need to concatenate the remainder of the previous call and
* the bytes of the current call. Use the end of the
* conversion buffer for this. */
fromlen = len + ip->bw_restlen;
from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen);
mch_memmove(from + ip->bw_restlen, buf, (size_t)len);
}
else
{
from = buf;
fromlen = len;
}
to = ip->bw_conv_buf;
if (enc_utf8)
{
/* Convert from UTF-8 to UCS-2, to the start of the buffer.
* The buffer has been allocated to be big enough. */
while (fromlen > 0)
{
n = (int)utf_ptr2len_len(from, (int)fromlen);
if (n > (int)fromlen) /* incomplete byte sequence */
break;
u8c = utf_ptr2char(from);
*to++ = (u8c & 0xff);
*to++ = (u8c >> 8);
fromlen -= n;
from += n;
}
/* Copy remainder to ip->bw_rest[] to be used for the next
* call. */
if (fromlen > CONV_RESTLEN)
{
/* weird overlong sequence */
ip->bw_conv_error = TRUE;
return FAIL;
}
mch_memmove(ip->bw_rest, from, fromlen);
ip->bw_restlen = (int)fromlen;
}
else
{
/* Convert from enc_codepage to UCS-2, to the start of the
* buffer. The buffer has been allocated to be big enough. */
ip->bw_restlen = 0;
needed = MultiByteToWideChar(enc_codepage,
MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)fromlen,
NULL, 0);
if (needed == 0)
{
/* When conversion fails there may be a trailing byte. */
needed = MultiByteToWideChar(enc_codepage,
MB_ERR_INVALID_CHARS, (LPCSTR)from, (int)fromlen - 1,
NULL, 0);
if (needed == 0)
{
/* Conversion doesn't work. */
ip->bw_conv_error = TRUE;
return FAIL;
}
/* Save the trailing byte for the next call. */
ip->bw_rest[0] = from[fromlen - 1];
ip->bw_restlen = 1;
}
needed = MultiByteToWideChar(enc_codepage, MB_ERR_INVALID_CHARS,
(LPCSTR)from, (int)(fromlen - ip->bw_restlen),
(LPWSTR)to, needed);
if (needed == 0)
{
/* Safety check: Conversion doesn't work. */
ip->bw_conv_error = TRUE;
return FAIL;
}
to += needed * 2;
}
fromlen = to - ip->bw_conv_buf;
buf = to;
# ifdef CP_UTF8 /* VC 4.1 doesn't define CP_UTF8 */
if (FIO_GET_CP(flags) == CP_UTF8)
{
/* Convert from UCS-2 to UTF-8, using the remainder of the
* conversion buffer. Fails when out of space. */
for (from = ip->bw_conv_buf; fromlen > 1; fromlen -= 2)
{
u8c = *from++;
u8c += (*from++ << 8);
to += utf_char2bytes(u8c, to);
if (to + 6 >= ip->bw_conv_buf + ip->bw_conv_buflen)
{
ip->bw_conv_error = TRUE;
return FAIL;
}
}
len = (int)(to - buf);
}
else
#endif
{
/* Convert from UCS-2 to the codepage, using the remainder of
* the conversion buffer. If the conversion uses the default
* character "0", the data doesn't fit in this encoding, so
* fail. */
len = WideCharToMultiByte(FIO_GET_CP(flags), 0,
(LPCWSTR)ip->bw_conv_buf, (int)fromlen / sizeof(WCHAR),
(LPSTR)to, (int)(ip->bw_conv_buflen - fromlen), 0,
&bad);
if (bad)
{
ip->bw_conv_error = TRUE;
return FAIL;
}
}
}
# endif
# ifdef MACOS_CONVERT
else if (flags & FIO_MACROMAN)
{
/*
* Convert UTF-8 or latin1 to Apple MacRoman.
*/
char_u *from;
size_t fromlen;
if (ip->bw_restlen > 0)
{
/* Need to concatenate the remainder of the previous call and
* the bytes of the current call. Use the end of the
* conversion buffer for this. */
fromlen = len + ip->bw_restlen;
from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen);
mch_memmove(from + ip->bw_restlen, buf, (size_t)len);
}
else
{
from = buf;
fromlen = len;
}
if (enc2macroman(from, fromlen,
ip->bw_conv_buf, &len, ip->bw_conv_buflen,
ip->bw_rest, &ip->bw_restlen) == FAIL)
{
ip->bw_conv_error = TRUE;
return FAIL;
}
buf = ip->bw_conv_buf;
}
# endif
# ifdef USE_ICONV
if (ip->bw_iconv_fd != (iconv_t)-1)
{
const char *from;
size_t fromlen;
char *to;
size_t tolen;
/* Convert with iconv(). */
if (ip->bw_restlen > 0)
{
/* Need to concatenate the remainder of the previous call and
* the bytes of the current call. Use the end of the
* conversion buffer for this. */
fromlen = len + ip->bw_restlen;
from = (char *)ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
mch_memmove((void *)from, ip->bw_rest, (size_t)ip->bw_restlen);
mch_memmove((void *)(from + ip->bw_restlen), buf, (size_t)len);
tolen = ip->bw_conv_buflen - fromlen;
}
else
{
from = (const char *)buf;
fromlen = len;
tolen = ip->bw_conv_buflen;
}
to = (char *)ip->bw_conv_buf;
if (ip->bw_first)
{
size_t save_len = tolen;
/* output the initial shift state sequence */
(void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen);
/* There is a bug in iconv() on Linux (which appears to be
* wide-spread) which sets "to" to NULL and messes up "tolen".
*/
if (to == NULL)
{
to = (char *)ip->bw_conv_buf;
tolen = save_len;
}
ip->bw_first = FALSE;
}
/*
* If iconv() has an error or there is not enough room, fail.
*/
if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen)
== (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL)
|| fromlen > CONV_RESTLEN)
{
ip->bw_conv_error = TRUE;
return FAIL;
}
/* copy remainder to ip->bw_rest[] to be used for the next call. */
if (fromlen > 0)
mch_memmove(ip->bw_rest, (void *)from, fromlen);
ip->bw_restlen = (int)fromlen;
buf = ip->bw_conv_buf;
len = (int)((char_u *)to - ip->bw_conv_buf);
}
# endif
}
#endif /* FEAT_MBYTE */
#ifdef FEAT_CRYPT
if (flags & FIO_ENCRYPTED) /* encrypt the data */
{
int ztemp, t, i;
for (i = 0; i < len; i++)
{
ztemp = buf[i];
buf[i] = ZENCODE(ztemp, t);
}
}
#endif
/* Repeat the write(), it may be interrupted by a signal. */
while (len > 0)
{
wlen = vim_write(ip->bw_fd, buf, len);
if (wlen <= 0) /* error! */
return FAIL;
len -= wlen;
buf += wlen;
}
return OK;
}
#ifdef FEAT_MBYTE
/*
* Convert a Unicode character to bytes.
*/
static int
ucs2bytes(c, pp, flags)
unsigned c; /* in: character */
char_u **pp; /* in/out: pointer to result */
int flags; /* FIO_ flags */
{
char_u *p = *pp;
int error = FALSE;
int cc;
if (flags & FIO_UCS4)
{
if (flags & FIO_ENDIAN_L)
{
*p++ = c;
*p++ = (c >> 8);
*p++ = (c >> 16);
*p++ = (c >> 24);
}
else
{
*p++ = (c >> 24);
*p++ = (c >> 16);
*p++ = (c >> 8);
*p++ = c;
}
}
else if (flags & (FIO_UCS2 | FIO_UTF16))
{
if (c >= 0x10000)
{
if (flags & FIO_UTF16)
{
/* 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;
cc = ((c >> 10) & 0x3ff) + 0xd800;
if (flags & FIO_ENDIAN_L)
{
*p++ = cc;
*p++ = ((unsigned)cc >> 8);
}
else
{
*p++ = ((unsigned)cc >> 8);
*p++ = cc;
}
c = (c & 0x3ff) + 0xdc00;
}
else
error = TRUE;
}
if (flags & FIO_ENDIAN_L)
{
*p++ = c;
*p++ = (c >> 8);
}
else
{
*p++ = (c >> 8);
*p++ = c;
}
}
else /* Latin1 */
{
if (c >= 0x100)
{
error = TRUE;
*p++ = 0xBF;
}
else
*p++ = c;
}
*pp = p;
return error;
}
/*
* Return TRUE if "a" and "b" are the same 'encoding'.
* Ignores difference between "ansi" and "latin1", "ucs-4" and "ucs-4be", etc.
*/
static int
same_encoding(a, b)
char_u *a;
char_u *b;
{
int f;
if (STRCMP(a, b) == 0)
return TRUE;
f = get_fio_flags(a);
return (f != 0 && get_fio_flags(b) == f);
}
/*
* 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(ptr)
char_u *ptr;
{
int prop;
if (*ptr == NUL)
ptr = p_enc;
prop = enc_canon_props(ptr);
if (prop & ENC_UNICODE)
{
if (prop & ENC_2BYTE)
{
if (prop & ENC_ENDIAN_L)
return FIO_UCS2 | FIO_ENDIAN_L;
return FIO_UCS2;
}
if (prop & ENC_4BYTE)
{
if (prop & ENC_ENDIAN_L)
return FIO_UCS4 | FIO_ENDIAN_L;
return FIO_UCS4;
}
if (prop & ENC_2WORD)
{
if (prop & ENC_ENDIAN_L)
return FIO_UTF16 | FIO_ENDIAN_L;
return FIO_UTF16;
}
return FIO_UTF8;
}
if (prop & ENC_LATIN1)
return FIO_LATIN1;
/* must be ENC_DBCS, requires iconv() */
return 0;
}
#ifdef WIN3264
/*
* Check "ptr" for a MS-Windows codepage name and return the FIO_ flags needed
* for the conversion MS-Windows can do for us. Also accept "utf-8".
* Used for conversion between 'encoding' and 'fileencoding'.
*/
static int
get_win_fio_flags(ptr)
char_u *ptr;
{
int cp;
/* Cannot do this when 'encoding' is not utf-8 and not a codepage. */
if (!enc_utf8 && enc_codepage <= 0)
return 0;
cp = encname2codepage(ptr);
if (cp == 0)
{
# ifdef CP_UTF8 /* VC 4.1 doesn't define CP_UTF8 */
if (STRCMP(ptr, "utf-8") == 0)
cp = CP_UTF8;
else
# endif
return 0;
}
return FIO_PUT_CP(cp) | FIO_CODEPAGE;
}
#endif
#ifdef MACOS_X
/*
* Check "ptr" for a Carbon supported encoding and return the FIO_ flags
* needed for the internal conversion to/from utf-8 or latin1.
*/
static int
get_mac_fio_flags(ptr)
char_u *ptr;
{
if ((enc_utf8 || STRCMP(p_enc, "latin1") == 0)
&& (enc_canon_props(ptr) & ENC_MACROMAN))
return FIO_MACROMAN;
return 0;
}
#endif
/*
* 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.
*/
static char_u *
check_for_bom(p, size, lenp, flags)
char_u *p;
long size;
int *lenp;
int flags;
{
char *name = NULL;
int len = 2;
if (p[0] == 0xef && p[1] == 0xbb && size >= 3 && p[2] == 0xbf
&& (flags == FIO_ALL || flags == 0))
{
name = "utf-8"; /* EF BB BF */
len = 3;
}
else if (p[0] == 0xff && p[1] == 0xfe)
{
if (size >= 4 && p[2] == 0 && p[3] == 0
&& (flags == FIO_ALL || flags == (FIO_UCS4 | FIO_ENDIAN_L)))
{
name = "ucs-4le"; /* FF FE 00 00 */
len = 4;
}
else if (flags == FIO_ALL || flags == (FIO_UCS2 | FIO_ENDIAN_L))
name = "ucs-2le"; /* FF FE */
else if (flags == (FIO_UTF16 | FIO_ENDIAN_L))
name = "utf-16le"; /* FF FE */
}
else if (p[0] == 0xfe && p[1] == 0xff
&& (flags == FIO_ALL || flags == FIO_UCS2 || flags == FIO_UTF16))
{
if (flags == FIO_UTF16)
name = "utf-16"; /* FE FF */
else
name = "ucs-2"; /* FE FF */
}
else if (size >= 4 && p[0] == 0 && p[1] == 0 && p[2] == 0xfe
&& p[3] == 0xff && (flags == FIO_ALL || flags == FIO_UCS4))
{
name = "ucs-4"; /* 00 00 FE FF */
len = 4;
}
*lenp = len;
return (char_u *)name;
}
/*
* Generate a BOM in "buf[4]" for encoding "name".
* Return the length of the BOM (zero when no BOM).
*/
static int
make_bom(buf, name)
char_u *buf;
char_u *name;
{
int flags;
char_u *p;
flags = get_fio_flags(name);
/* Can't put a BOM in a non-Unicode file. */
if (flags == FIO_LATIN1 || flags == 0)
return 0;
if (flags == FIO_UTF8) /* UTF-8 */
{
buf[0] = 0xef;
buf[1] = 0xbb;
buf[2] = 0xbf;
return 3;
}
p = buf;
(void)ucs2bytes(0xfeff, &p, flags);
return (int)(p - buf);
}
#endif
/*
* Try to find a shortname by comparing the fullname with the current
* directory.
* Returns NULL if not shorter name possible, pointer into "full_path"
* otherwise.
*/
char_u *
shorten_fname(full_path, dir_name)
char_u *full_path;
char_u *dir_name;
{
int len;
char_u *p;
if (full_path == NULL)
return NULL;
len = (int)STRLEN(dir_name);
if (fnamencmp(dir_name, full_path, len) == 0)
{
p = full_path + len;
#if defined(MSDOS) || defined(MSWIN) || defined(OS2)
/*
* MSDOS: when a file is in the root directory, dir_name will end in a
* slash, since C: by itself does not define a specific dir. In this
* case p may already be correct. <negri>
*/
if (!((len > 2) && (*(p - 2) == ':')))
#endif
{
if (vim_ispathsep(*p))
++p;
#ifndef VMS /* the path separator is always part of the path */
else
p = NULL;
#endif
}
}
#if defined(MSDOS) || defined(MSWIN) || defined(OS2)
/*
* When using a file in the current drive, remove the drive name:
* "A:\dir\file" -> "\dir\file". This helps when moving a session file on
* a floppy from "A:\dir" to "B:\dir".
*/
else if (len > 3
&& TOUPPER_LOC(full_path[0]) == TOUPPER_LOC(dir_name[0])
&& full_path[1] == ':'
&& vim_ispathsep(full_path[2]))
p = full_path + 2;
#endif
else
p = NULL;
return p;
}
/*
* Shorten filenames for all buffers.
* 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.
* For buffers that have buftype "nofile" or "scratch": never change the file
* name.
*/
void
shorten_fnames(force)
int force;
{
char_u dirname[MAXPATHL];
buf_T *buf;
char_u *p;
mch_dirname(dirname, MAXPATHL);
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
{
if (buf->b_fname != NULL
#ifdef FEAT_QUICKFIX
&& !bt_nofile(buf)
#endif
&& !path_with_url(buf->b_fname)
&& (force
|| buf->b_sfname == NULL
|| mch_isFullName(buf->b_sfname)))
{
vim_free(buf->b_sfname);
buf->b_sfname = NULL;
p = shorten_fname(buf->b_ffname, dirname);
if (p != NULL)
{
buf->b_sfname = vim_strsave(p);
buf->b_fname = buf->b_sfname;
}
if (p == NULL || buf->b_fname == NULL)
buf->b_fname = buf->b_ffname;
}
/* Always make the swap file name a full path, a "nofile" buffer may
* also have a swap file. */
mf_fullname(buf->b_ml.ml_mfp);
}
#ifdef FEAT_WINDOWS
status_redraw_all();
redraw_tabline = TRUE;
#endif
}
#if (defined(FEAT_DND) && defined(FEAT_GUI_GTK)) \
|| defined(FEAT_GUI_MSWIN) \
|| defined(FEAT_GUI_MAC) \
|| defined(PROTO)
/*
* Shorten all filenames in "fnames[count]" by current directory.
*/
void
shorten_filenames(fnames, count)
char_u **fnames;
int count;
{
int i;
char_u dirname[MAXPATHL];
char_u *p;
if (fnames == NULL || count < 1)
return;
mch_dirname(dirname, sizeof(dirname));
for (i = 0; i < count; ++i)
{
if ((p = shorten_fname(fnames[i], dirname)) != NULL)
{
/* shorten_fname() returns pointer in given "fnames[i]". If free
* "fnames[i]" first, "p" becomes invalid. So we need to copy
* "p" first then free fnames[i]. */
p = vim_strsave(p);
vim_free(fnames[i]);
fnames[i] = p;
}
}
}
#endif
/*
* add extention to file name - change path/fo.o.h to path/fo.o.h.ext or
* fo_o_h.ext for MSDOS or when shortname option set.
*
* Assumed that fname is a valid name found in the filesystem we assure that
* the return value is a different name and ends in 'ext'.
* "ext" MUST be at most 4 characters long if it starts with a dot, 3
* characters otherwise.
* Space for the returned name is allocated, must be freed later.
* Returns NULL when out of memory.
*/
char_u *
modname(fname, ext, prepend_dot)
char_u *fname, *ext;
int prepend_dot; /* may prepend a '.' to file name */
{
return buf_modname(
#ifdef SHORT_FNAME
TRUE,
#else
(curbuf->b_p_sn || curbuf->b_shortname),
#endif
fname, ext, prepend_dot);
}
char_u *
buf_modname(shortname, fname, ext, prepend_dot)
int shortname; /* use 8.3 file name */
char_u *fname, *ext;
int prepend_dot; /* may prepend a '.' to file name */
{
char_u *retval;
char_u *s;
char_u *e;
char_u *ptr;
int fnamelen, extlen;
extlen = (int)STRLEN(ext);
/*
* If there is no file name we must get the name of the current directory
* (we need the full path in case :cd is used).
*/
if (fname == NULL || *fname == NUL)
{
retval = alloc((unsigned)(MAXPATHL + extlen + 3));
if (retval == NULL)
return NULL;
if (mch_dirname(retval, MAXPATHL) == FAIL ||
(fnamelen = (int)STRLEN(retval)) == 0)
{
vim_free(retval);
return NULL;
}
if (!after_pathsep(retval, retval + fnamelen))
{
retval[fnamelen++] = PATHSEP;
retval[fnamelen] = NUL;
}
#ifndef SHORT_FNAME
prepend_dot = FALSE; /* nothing to prepend a dot to */
#endif
}
else
{
fnamelen = (int)STRLEN(fname);
retval = alloc((unsigned)(fnamelen + extlen + 3));
if (retval == NULL)
return NULL;
STRCPY(retval, fname);
#ifdef VMS
vms_remove_version(retval); /* we do not need versions here */
#endif
}
/*
* search backwards until we hit a '/', '\' or ':' replacing all '.'
* by '_' for MSDOS or when shortname option set and ext starts with a dot.
* Then truncate what is after the '/', '\' or ':' to 8 characters for
* MSDOS and 26 characters for AMIGA, a lot more for UNIX.
*/
for (ptr = retval + fnamelen; ptr > retval; mb_ptr_back(retval, ptr))
{
#ifndef RISCOS
if (*ext == '.'
# ifdef USE_LONG_FNAME
&& (!USE_LONG_FNAME || shortname)
# else
# ifndef SHORT_FNAME
&& shortname
# endif
# endif
)
if (*ptr == '.') /* replace '.' by '_' */
*ptr = '_';
#endif
if (vim_ispathsep(*ptr))
{
++ptr;
break;
}
}
/* the file name has at most BASENAMELEN characters. */
#ifndef SHORT_FNAME
if (STRLEN(ptr) > (unsigned)BASENAMELEN)
ptr[BASENAMELEN] = '\0';
#endif
s = ptr + STRLEN(ptr);
/*
* For 8.3 file names we may have to reduce the length.
*/
#ifdef USE_LONG_FNAME
if (!USE_LONG_FNAME || shortname)
#else
# ifndef SHORT_FNAME
if (shortname)
# endif
#endif
{
/*
* If there is no file name, or the file name ends in '/', and the
* extension starts with '.', put a '_' before the dot, because just
* ".ext" is invalid.
*/
if (fname == NULL || *fname == NUL
|| vim_ispathsep(fname[STRLEN(fname) - 1]))
{
#ifdef RISCOS
if (*ext == '/')
#else
if (*ext == '.')
#endif
*s++ = '_';
}
/*
* If the extension starts with '.', truncate the base name at 8
* characters
*/
#ifdef RISCOS
/* We normally use '/', but swap files are '_' */
else if (*ext == '/' || *ext == '_')
#else
else if (*ext == '.')
#endif
{
if (s - ptr > (size_t)8)
{
s = ptr + 8;
*s = '\0';
}
}
/*
* If the extension doesn't start with '.', and the file name
* doesn't have an extension yet, append a '.'
*/
#ifdef RISCOS
else if ((e = vim_strchr(ptr, '/')) == NULL)
*s++ = '/';
#else
else if ((e = vim_strchr(ptr, '.')) == NULL)
*s++ = '.';
#endif
/*
* If the extension doesn't start with '.', and there already is an
* extension, it may need to be truncated
*/
else if ((int)STRLEN(e) + extlen > 4)
s = e + 4 - extlen;
}
#if defined(OS2) || defined(USE_LONG_FNAME) || 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 extention.
* ext can start with '.' and cannot exceed 3 more characters.
*/
STRCPY(s, ext);
#ifndef SHORT_FNAME
/*
* Prepend the dot.
*/
if (prepend_dot && !shortname && *(e = gettail(retval)) !=
#ifdef RISCOS
'/'
#else
'.'
#endif
#ifdef USE_LONG_FNAME
&& USE_LONG_FNAME
#endif
)
{
mch_memmove(e + 1, e, STRLEN(e) + 1);
#ifdef RISCOS
*e = '/';
#else
*e = '.';
#endif
}
#endif
/*
* Check that, after appending the extension, the file name is really
* different.
*/
if (fname != NULL && STRCMP(fname, retval) == 0)
{
/* we search for a character that can be replaced by '_' */
while (--s >= ptr)
{
if (*s != '_')
{
*s = '_';
break;
}
}
if (s < ptr) /* fname was "________.<ext>", how tricky! */
*ptr = 'v';
}
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(buf, size, fp)
char_u *buf;
int size;
FILE *fp;
{
char *eof;
#define FGETS_SIZE 200
char tbuf[FGETS_SIZE];
buf[size - 2] = NUL;
#ifdef USE_CR
eof = fgets_cr((char *)buf, size, fp);
#else
eof = fgets((char *)buf, size, fp);
#endif
if (buf[size - 2] != NUL && buf[size - 2] != '\n')
{
buf[size - 1] = NUL; /* Truncate the line */
/* Now throw away the rest of the line: */
do
{
tbuf[FGETS_SIZE - 2] = NUL;
#ifdef USE_CR
fgets_cr((char *)tbuf, FGETS_SIZE, fp);
#else
fgets((char *)tbuf, FGETS_SIZE, fp);
#endif
} while (tbuf[FGETS_SIZE - 2] != NUL && tbuf[FGETS_SIZE - 2] != '\n');
}
return (eof == NULL);
}
#if defined(USE_CR) || defined(PROTO)
/*
* Like vim_fgets(), but accept any line terminator: CR, CR-LF or LF.
* Returns TRUE for end-of-file.
* Only used for the Mac, because it's much slower than vim_fgets().
*/
int
tag_fgets(buf, size, fp)
char_u *buf;
int size;
FILE *fp;
{
int i = 0;
int c;
int eof = FALSE;
for (;;)
{
c = fgetc(fp);
if (c == EOF)
{
eof = TRUE;
break;
}
if (c == '\r')
{
/* Always store a NL for end-of-line. */
if (i < size - 1)
buf[i++] = '\n';
c = fgetc(fp);
if (c != '\n') /* Macintosh format: single CR. */
ungetc(c, fp);
break;
}
if (i < size - 1)
buf[i++] = c;
if (c == '\n')
break;
}
buf[i] = NUL;
return eof;
}
#endif
/*
* 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.
*/
int
vim_rename(from, to)
char_u *from;
char_u *to;
{
int fd_in;
int fd_out;
int n;
char *errmsg = NULL;
char *buffer;
#ifdef AMIGA
BPTR flock;
#endif
struct stat st;
long perm;
#ifdef HAVE_ACL
vim_acl_T acl; /* ACL from original file */
#endif
/*
* When the names are identical, there is nothing to do.
*/
if (fnamecmp(from, to) == 0)
return 0;
/*
* Fail if the "from" file doesn't exist. Avoids that "to" is deleted.
*/
if (mch_stat((char *)from, &st) < 0)
return -1;
/*
* Delete the "to" file, this is required on some systems to make the
* mch_rename() work, on other systems it makes sure that we don't have
* two files when the mch_rename() fails.
*/
#ifdef AMIGA
/*
* With MSDOS-compatible filesystems (crossdos, messydos) it is possible
* that the name of the "to" file is the same as the "from" file, even
* though the names are different. To avoid the chance of accidentally
* deleting the "from" file (horror!) we lock it during the remove.
*
* When used for making a backup before writing the file: This should not
* happen with ":w", because startscript() should detect this problem and
* set buf->b_shortname, causing modname() to return a correct ".bak" file
* name. This problem does exist with ":w filename", but then the
* original file will be somewhere else so the backup isn't really
* important. If autoscripting is off the rename may fail.
*/
flock = Lock((UBYTE *)from, (long)ACCESS_READ);
#endif
mch_remove(to);
#ifdef AMIGA
if (flock)
UnLock(flock);
#endif
/*
* First try a normal rename, return if it works.
*/
if (mch_rename((char *)from, (char *)to) == 0)
return 0;
/*
* Rename() failed, try copying the file.
*/
perm = mch_getperm(from);
#ifdef HAVE_ACL
/* For systems that support ACL: get the ACL from the original file. */
acl = mch_get_acl(from);
#endif
fd_in = mch_open((char *)from, O_RDONLY|O_EXTRA, 0);
if (fd_in == -1)
return -1;
/* Create the new file with same permissions as the original. */
fd_out = mch_open((char *)to,
O_CREAT|O_EXCL|O_WRONLY|O_EXTRA|O_NOFOLLOW, (int)perm);
if (fd_out == -1)
{
close(fd_in);
return -1;
}
buffer = (char *)alloc(BUFSIZE);
if (buffer == NULL)
{
close(fd_in);
close(fd_out);
return -1;
}
while ((n = vim_read(fd_in, buffer, BUFSIZE)) > 0)
if (vim_write(fd_out, buffer, n) != n)
{
errmsg = _("E208: Error writing to \"%s\"");
break;
}
vim_free(buffer);
close(fd_in);
if (close(fd_out) < 0)
errmsg = _("E209: Error closing \"%s\"");
if (n < 0)
{
errmsg = _("E210: Error reading \"%s\"");
to = from;
}
#ifndef UNIX /* for Unix mch_open() already set the permission */
mch_setperm(to, perm);
#endif
#ifdef HAVE_ACL
mch_set_acl(to, acl);
#endif
if (errmsg != NULL)
{
EMSG2(errmsg, to);
return -1;
}
mch_remove(from);
return 0;
}
static int already_warned = FALSE;
/*
* Check if any not hidden buffer has been changed.
* Postpone the check if there are characters in the stuff buffer, a global
* command is being executed, a mapping is being executed or an autocommand is
* busy.
* Returns TRUE if some message was written (screen should be redrawn and
* cursor positioned).
*/
int
check_timestamps(focus)
int focus; /* called for GUI focus event */
{
buf_T *buf;
int didit = 0;
int n;
/* Don't check timestamps while system() or another low-level function may
* cause us to lose and gain focus. */
if (no_check_timestamps > 0)
return FALSE;
/* Avoid doing a check twice. The OK/Reload dialog can cause a focus
* event and we would keep on checking if the file is steadily growing.
* Do check again after typing something. */
if (focus && did_check_timestamps)
{
need_check_timestamps = TRUE;
return FALSE;
}
if (!stuff_empty() || global_busy || !typebuf_typed()
#ifdef FEAT_AUTOCMD
|| autocmd_busy || curbuf_lock > 0
#endif
)
need_check_timestamps = TRUE; /* check later */
else
{
++no_wait_return;
did_check_timestamps = TRUE;
already_warned = FALSE;
for (buf = firstbuf; buf != NULL; )
{
/* Only check buffers in a window. */
if (buf->b_nwindows > 0)
{
n = buf_check_timestamp(buf, focus);
if (didit < n)
didit = n;
if (n > 0 && !buf_valid(buf))
{
/* Autocommands have removed the buffer, start at the
* first one again. */
buf = firstbuf;
continue;
}
}
buf = buf->b_next;
}
--no_wait_return;
need_check_timestamps = FALSE;
if (need_wait_return && didit == 2)
{
/* make sure msg isn't overwritten */
msg_puts((char_u *)"\n");
out_flush();
}
}
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.
*/
static int
move_lines(frombuf, tobuf)
buf_T *frombuf;
buf_T *tobuf;
{
buf_T *tbuf = curbuf;
int retval = OK;
linenr_T lnum;
char_u *p;
/* Copy the lines in "frombuf" to "tobuf". */
curbuf = tobuf;
for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; ++lnum)
{
p = vim_strsave(ml_get_buf(frombuf, lnum, FALSE));
if (p == NULL || ml_append(lnum - 1, p, 0, FALSE) == FAIL)
{
vim_free(p);
retval = FAIL;
break;
}
vim_free(p);
}
/* Delete all the lines in "frombuf". */
if (retval != FAIL)
{
curbuf = frombuf;
for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; --lnum)
if (ml_delete(lnum, FALSE) == FAIL)
{
/* Oops! We could try putting back the saved lines, but that
* might fail again... */
retval = FAIL;
break;
}
}
curbuf = tbuf;
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.
*/
/*ARGSUSED*/
int
buf_check_timestamp(buf, focus)
buf_T *buf;
int focus; /* called for GUI focus event */
{
struct stat st;
int stat_res;
int retval = 0;
char_u *path;
char_u *tbuf;
char *mesg = NULL;
char *mesg2 = "";
int helpmesg = FALSE;
int reload = FALSE;
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
int can_reload = FALSE;
#endif
size_t orig_size = buf->b_orig_size;
int orig_mode = buf->b_orig_mode;
#ifdef FEAT_GUI
int save_mouse_correct = need_mouse_correct;
#endif
#ifdef FEAT_AUTOCMD
static int busy = FALSE;
int n;
char_u *s;
#endif
char *reason;
/* If there is no file name, the buffer is not loaded, 'buftype' is
* set, we are in the middle of a save or being called recursively: ignore
* this buffer. */
if (buf->b_ffname == NULL
|| buf->b_ml.ml_mfp == NULL
#if defined(FEAT_QUICKFIX)
|| *buf->b_p_bt != NUL
#endif
|| buf->b_saving
#ifdef FEAT_AUTOCMD
|| busy
#endif
#ifdef FEAT_NETBEANS_INTG
|| isNetbeansBuffer(buf)
#endif
)
return 0;
if ( !(buf->b_flags & BF_NOTEDITED)
&& buf->b_mtime != 0
&& ((stat_res = mch_stat((char *)buf->b_ffname, &st)) < 0
|| time_differs((long)st.st_mtime, buf->b_mtime)
#ifdef HAVE_ST_MODE
|| (int)st.st_mode != buf->b_orig_mode
#else
|| mch_getperm(buf->b_ffname) != buf->b_orig_mode
#endif
))
{
retval = 1;
/* set b_mtime to stop further warnings (e.g., when executing
* FileChangedShell autocmd) */
if (stat_res < 0)
{
buf->b_mtime = 0;
buf->b_orig_size = 0;
buf->b_orig_mode = 0;
}
else
buf_store_time(buf, &st, buf->b_ffname);
/* Don't do anything for a directory. Might contain the file
* explorer. */
if (mch_isdir(buf->b_fname))
;
/*
* 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.
*/
else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar)
&& !bufIsChanged(buf) && stat_res >= 0)
reload = TRUE;
else
{
if (stat_res < 0)
reason = "deleted";
else if (bufIsChanged(buf))
reason = "conflict";
else if (orig_size != buf->b_orig_size || buf_contents_changed(buf))
reason = "changed";
else if (orig_mode != buf->b_orig_mode)
reason = "mode";
else
reason = "time";
#ifdef FEAT_AUTOCMD
/*
* Only give the warning if there are no FileChangedShell
* autocommands.
* Avoid being called recursively by setting "busy".
*/
busy = TRUE;
# ifdef FEAT_EVAL
set_vim_var_string(VV_FCS_REASON, (char_u *)reason, -1);
set_vim_var_string(VV_FCS_CHOICE, (char_u *)"", -1);
# endif
n = apply_autocmds(EVENT_FILECHANGEDSHELL,
buf->b_fname, buf->b_fname, FALSE, buf);
busy = FALSE;
if (n)
{
if (!buf_valid(buf))
EMSG(_("E246: FileChangedShell autocommand deleted buffer"));
# ifdef FEAT_EVAL
s = get_vim_var_str(VV_FCS_CHOICE);
if (STRCMP(s, "reload") == 0 && *reason != 'd')
reload = TRUE;
else if (STRCMP(s, "ask") == 0)
n = FALSE;
else
# endif
return 2;
}
if (!n)
#endif
{
if (*reason == 'd')
mesg = _("E211: File \"%s\" no longer available");
else
{
helpmesg = TRUE;
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
can_reload = TRUE;
#endif
/*
* Check if the file contents really changed to avoid
* giving a warning when only the timestamp was set (e.g.,
* checked out of CVS). Always warn when the buffer was
* changed.
*/
if (reason[2] == 'n')
{
mesg = _("W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
mesg2 = _("See \":help W12\" for more info.");
}
else if (reason[1] == 'h')
{
mesg = _("W11: Warning: File \"%s\" has changed since editing started");
mesg2 = _("See \":help W11\" for more info.");
}
else if (*reason == 'm')
{
mesg = _("W16: Warning: Mode of file \"%s\" has changed since editing started");
mesg2 = _("See \":help W16\" for more info.");
}
/* Else: only timestamp changed, ignored */
}
}
}
}
else if ((buf->b_flags & BF_NEW) && !(buf->b_flags & BF_NEW_W)
&& vim_fexists(buf->b_ffname))
{
retval = 1;
mesg = _("W13: Warning: File \"%s\" has been created after editing started");
buf->b_flags |= BF_NEW_W;
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
can_reload = TRUE;
#endif
}
if (mesg != NULL)
{
path = home_replace_save(buf, buf->b_fname);
if (path != NULL)
{
if (!helpmesg)
mesg2 = "";
tbuf = alloc((unsigned)(STRLEN(path) + STRLEN(mesg)
+ STRLEN(mesg2) + 2));
sprintf((char *)tbuf, mesg, path);
#if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG)
if (can_reload)
{
if (*mesg2 != NUL)
{
STRCAT(tbuf, "\n");
STRCAT(tbuf, mesg2);
}
if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), tbuf,
(char_u *)_("&OK\n&Load File"), 1, NULL) == 2)
reload = TRUE;
}
else
#endif
if (State > NORMAL_BUSY || (State & CMDLINE) || already_warned)
{
if (*mesg2 != NUL)
{
STRCAT(tbuf, "; ");
STRCAT(tbuf, mesg2);
}
EMSG(tbuf);
retval = 2;
}
else
{
# ifdef FEAT_AUTOCMD
if (!autocmd_busy)
# endif
{
msg_start();
msg_puts_attr(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)
{
out_flush();
# ifdef FEAT_GUI
if (!focus)
# endif
/* give the user some time to think about it */
ui_delay(1000L, TRUE);
/* don't redraw and erase the message */
redraw_cmdline = FALSE;
}
}
already_warned = TRUE;
}
vim_free(path);
vim_free(tbuf);
}
}
if (reload)
/* Reload the buffer. */
buf_reload(buf, orig_mode);
#ifdef FEAT_AUTOCMD
if (buf_valid(buf))
(void)apply_autocmds(EVENT_FILECHANGEDSHELLPOST,
buf->b_fname, buf->b_fname, FALSE, buf);
#endif
#ifdef FEAT_GUI
/* restore this in case an autocommand has set it; it would break
* 'mousefocus' */
need_mouse_correct = save_mouse_correct;
#endif
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, orig_mode)
buf_T *buf;
int orig_mode;
{
exarg_T ea;
pos_T old_cursor;
linenr_T old_topline;
int old_ro = buf->b_p_ro;
buf_T *savebuf;
int saved = OK;
aco_save_T aco;
/* 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. */
if (prep_exarg(&ea, buf) == OK)
{
old_cursor = curwin->w_cursor;
old_topline = curwin->w_topline;
/*
* To behave like when a new file is edited (matters for
* BufReadPost autocommands) we first need to delete the current
* buffer contents. But if reading the file fails we should keep
* the old contents. Can't use memory only, the file might be
* too big. Use a hidden buffer to move the buffer contents to.
*/
if (bufempty())
savebuf = NULL;
else
{
/* Allocate a buffer without putting it in the buffer list. */
savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
if (savebuf != NULL && buf == curbuf)
{
/* Open the memline. */
curbuf = savebuf;
curwin->w_buffer = savebuf;
saved = ml_open(curbuf);
curbuf = buf;
curwin->w_buffer = buf;
}
if (savebuf == NULL || saved == FAIL || buf != curbuf
|| move_lines(buf, savebuf) == FAIL)
{
EMSG2(_("E462: Could not prepare for reloading \"%s\""),
buf->b_fname);
saved = FAIL;
}
}
if (saved == OK)
{
curbuf->b_flags |= BF_CHECK_RO; /* check for RO again */
#ifdef FEAT_AUTOCMD
keep_filetype = TRUE; /* don't detect 'filetype' */
#endif
if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0,
(linenr_T)0,
(linenr_T)MAXLNUM, &ea, READ_NEW) == FAIL)
{
#if defined(FEAT_AUTOCMD) && defined(FEAT_EVAL)
if (!aborting())
#endif
EMSG2(_("E321: Could not reload \"%s\""), buf->b_fname);
if (savebuf != NULL && buf_valid(savebuf) && buf == curbuf)
{
/* Put the text back from the save buffer. First
* delete any lines that readfile() added. */
while (!bufempty())
if (ml_delete(buf->b_ml.ml_line_count, FALSE) == FAIL)
break;
(void)move_lines(savebuf, buf);
}
}
else if (buf == curbuf)
{
/* Mark the buffer as unmodified and free undo info. */
unchanged(buf, TRUE);
u_blockfree(buf);
u_clearall(buf);
}
}
vim_free(ea.cmd);
if (savebuf != NULL && buf_valid(savebuf))
wipe_buffer(savebuf, FALSE);
#ifdef FEAT_DIFF
/* Invalidate diff info if necessary. */
diff_invalidate(curbuf);
#endif
/* Restore the topline and cursor position and check it (lines may
* have been removed). */
if (old_topline > curbuf->b_ml.ml_line_count)
curwin->w_topline = curbuf->b_ml.ml_line_count;
else
curwin->w_topline = old_topline;
curwin->w_cursor = old_cursor;
check_cursor();
update_topline();
#ifdef FEAT_AUTOCMD
keep_filetype = FALSE;
#endif
#ifdef FEAT_FOLDING
{
win_T *wp;
/* Update folds unless they are defined manually. */
FOR_ALL_WINDOWS(wp)
if (wp->w_buffer == curwin->w_buffer
&& !foldmethodIsManual(wp))
foldUpdateAll(wp);
}
#endif
/* If the mode didn't change and 'readonly' was set, keep the old
* value; the user probably used the ":view" command. But don't
* reset it, might have had a read error. */
if (orig_mode == curbuf->b_orig_mode)
curbuf->b_p_ro |= old_ro;
}
/* restore curwin/curbuf and a few other things */
aucmd_restbuf(&aco);
/* Careful: autocommands may have made "buf" invalid! */
}
/*ARGSUSED*/
void
buf_store_time(buf, st, fname)
buf_T *buf;
struct stat *st;
char_u *fname;
{
buf->b_mtime = (long)st->st_mtime;
buf->b_orig_size = (size_t)st->st_size;
#ifdef HAVE_ST_MODE
buf->b_orig_mode = (int)st->st_mode;
#else
buf->b_orig_mode = mch_getperm(fname);
#endif
}
/*
* 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(offset)
linenr_T offset;
{
if (write_no_eol_lnum != 0) /* only if there is a missing eol */
write_no_eol_lnum += offset;
}
#if defined(TEMPDIRNAMES) || defined(PROTO)
static long temp_count = 0; /* Temp filename counter. */
/*
* Delete the temp directory and all files it contains.
*/
void
vim_deltempdir()
{
char_u **files;
int file_count;
int i;
if (vim_tempdir != NULL)
{
sprintf((char *)NameBuff, "%s*", vim_tempdir);
if (gen_expand_wildcards(1, &NameBuff, &file_count, &files,
EW_DIR|EW_FILE|EW_SILENT) == OK)
{
for (i = 0; i < file_count; ++i)
mch_remove(files[i]);
FreeWild(file_count, files);
}
gettail(NameBuff)[-1] = NUL;
(void)mch_rmdir(NameBuff);
vim_free(vim_tempdir);
vim_tempdir = NULL;
}
}
#endif
/*
* vim_tempname(): Return a unique name that can be used for a temp file.
*
* The temp file is NOT created.
*
* The returned pointer is to allocated memory.
* The returned pointer is NULL if no valid name was found.
*/
/*ARGSUSED*/
char_u *
vim_tempname(extra_char)
int extra_char; /* character to use in the name instead of '?' */
{
#ifdef USE_TMPNAM
char_u itmp[L_tmpnam]; /* use tmpnam() */
#else
char_u itmp[TEMPNAMELEN];
#endif
#ifdef TEMPDIRNAMES
static char *(tempdirs[]) = {TEMPDIRNAMES};
int i;
long nr;
long off;
# ifndef EEXIST
struct stat st;
# endif
/*
* This will create a directory for private use by this instance of Vim.
* 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.
*/
if (vim_tempdir == NULL)
{
/*
* Try the entries in TEMPDIRNAMES to create the temp directory.
*/
for (i = 0; i < sizeof(tempdirs) / sizeof(char *); ++i)
{
/* expand $TMP, leave room for "/v1100000/999999999" */
expand_env((char_u *)tempdirs[i], itmp, TEMPNAMELEN - 20);
if (mch_isdir(itmp)) /* directory exists */
{
# ifdef __EMX__
/* If $TMP contains a forward slash (perhaps using bash or
* tcsh), don't add a backslash, use a forward slash!
* Adding 2 backslashes didn't work. */
if (vim_strchr(itmp, '/') != NULL)
STRCAT(itmp, "/");
else
# endif
add_pathsep(itmp);
/* Get an arbitrary number of up to 6 digits. When it's
* unlikely that it already exists it will be faster,
* otherwise it doesn't matter. The use of mkdir() avoids any
* security problems because of the predictable number. */
nr = (mch_get_pid() + (long)time(NULL)) % 1000000L;
/* Try up to 10000 different values until we find a name that
* doesn't exist. */
for (off = 0; off < 10000L; ++off)
{
int r;
#if defined(UNIX) || defined(VMS)
mode_t umask_save;
#endif
sprintf((char *)itmp + STRLEN(itmp), "v%ld", nr + off);
# ifndef EEXIST
/* If mkdir() does not set errno to EEXIST, check for
* existing file here. There is a race condition then,
* although it's fail-safe. */
if (mch_stat((char *)itmp, &st) >= 0)
continue;
# endif
#if defined(UNIX) || defined(VMS)
/* Make sure the umask doesn't remove the executable bit.
* "repl" has been reported to use "177". */
umask_save = umask(077);
#endif
r = vim_mkdir(itmp, 0700);
#if defined(UNIX) || defined(VMS)
(void)umask(umask_save);
#endif
if (r == 0)
{
char_u *buf;
/* Directory was created, use this name.
* Expand to full path; When using the current
* directory a ":cd" would confuse us. */
buf = alloc((unsigned)MAXPATHL + 1);
if (buf != NULL)
{
if (vim_FullName(itmp, buf, MAXPATHL, FALSE)
== FAIL)
STRCPY(buf, itmp);
# ifdef __EMX__
if (vim_strchr(buf, '/') != NULL)
STRCAT(buf, "/");
else
# endif
add_pathsep(buf);
vim_tempdir = vim_strsave(buf);
vim_free(buf);
}
break;
}
# ifdef EEXIST
/* If the mkdir() didn't fail because the file/dir exists,
* we probably can't create any dir here, try another
* place. */
if (errno != EEXIST)
# endif
break;
}
if (vim_tempdir != NULL)
break;
}
}
}
if (vim_tempdir != NULL)
{
/* There is no need to check if the file exists, because we own the
* directory and nobody else creates a file in it. */
sprintf((char *)itmp, "%s%ld", vim_tempdir, temp_count++);
return vim_strsave(itmp);
}
return NULL;
#else /* TEMPDIRNAMES */
# ifdef WIN3264
char szTempFile[_MAX_PATH + 1];
char buf4[4];
char_u *retval;
char_u *p;
STRCPY(itmp, "");
if (GetTempPath(_MAX_PATH, szTempFile) == 0)
szTempFile[0] = NUL; /* GetTempPath() failed, use current dir */
strcpy(buf4, "VIM");
buf4[2] = extra_char; /* make it "VIa", "VIb", etc. */
if (GetTempFileName(szTempFile, buf4, 0, itmp) == 0)
return NULL;
/* GetTempFileName() will create the file, we don't want that */
(void)DeleteFile(itmp);
/* Backslashes in a temp file name cause problems when filtering with
* "sh". NOTE: This also checks 'shellcmdflag' to help those people who
* didn't set 'shellslash'. */
retval = vim_strsave(itmp);
if (*p_shcf == '-' || p_ssl)
for (p = retval; *p; ++p)
if (*p == '\\')
*p = '/';
return retval;
# else /* WIN3264 */
# ifdef USE_TMPNAM
/* tmpnam() will make its own name */
if (*tmpnam((char *)itmp) == NUL)
return NULL;
# else
char_u *p;
# ifdef VMS_TEMPNAM
/* mktemp() is not working on VMS. It seems to be
* a do-nothing function. Therefore we use tempnam().
*/
sprintf((char *)itmp, "VIM%c", extra_char);
p = (char_u *)tempnam("tmp:", (char *)itmp);
if (p != NULL)
{
/* VMS will use '.LOG' if we don't explicitly specify an extension,
* and VIM will then be unable to find the file later */
STRCPY(itmp, p);
STRCAT(itmp, ".txt");
free(p);
}
else
return NULL;
# else
STRCPY(itmp, TEMPNAME);
if ((p = vim_strchr(itmp, '?')) != NULL)
*p = extra_char;
if (mktemp((char *)itmp) == NULL)
return NULL;
# endif
# endif
return vim_strsave(itmp);
# endif /* WIN3264 */
#endif /* TEMPDIRNAMES */
}
#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO)
/*
* Convert all backslashes in fname to forward slashes in-place.
*/
void
forward_slash(fname)
char_u *fname;
{
char_u *p;
for (p = fname; *p != NUL; ++p)
# ifdef FEAT_MBYTE
/* The Big5 encoding can have '\' in the trail byte. */
if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1)
++p;
else
# endif
if (*p == '\\')
*p = '/';
}
#endif
/*
* Code for automatic commands.
*
* Only included when "FEAT_AUTOCMD" has been defined.
*/
#if defined(FEAT_AUTOCMD) || defined(PROTO)
/*
* The autocommands are stored in a list for each event.
* Autocommands for the same pattern, that are consecutive, are joined
* together, to avoid having to match the pattern too often.
* The result is an array of Autopat lists, which point to AutoCmd lists:
*
* first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
* Autopat.cmds Autopat.cmds
* | |
* V V
* AutoCmd.next AutoCmd.next
* | |
* V V
* AutoCmd.next NULL
* |
* V
* NULL
*
* first_autopat[1] --> Autopat.next --> NULL
* Autopat.cmds
* |
* V
* AutoCmd.next
* |
* V
* NULL
* etc.
*
* The order of AutoCmds is important, this is the order in which they were
* defined and will have to be executed.
*/
typedef struct AutoCmd
{
char_u *cmd; /* The command to be executed (NULL
when command has been removed) */
char nested; /* If autocommands nest here */
char last; /* last command in list */
#ifdef FEAT_EVAL
scid_T scriptID; /* script ID where defined */
#endif
struct AutoCmd *next; /* Next AutoCmd in list */
} AutoCmd;
typedef struct AutoPat
{
int group; /* group ID */
char_u *pat; /* pattern as typed (NULL when pattern
has been removed) */
int patlen; /* strlen() of pat */
regprog_T *reg_prog; /* compiled regprog for pattern */
char allow_dirs; /* Pattern may match whole path */
char last; /* last pattern for apply_autocmds() */
AutoCmd *cmds; /* list of commands to do */
struct AutoPat *next; /* next AutoPat in AutoPat list */
int buflocal_nr; /* !=0 for buffer-local AutoPat */
} AutoPat;
static struct event_name
{
char *name; /* event name */
event_T event; /* event number */
} event_names[] =
{
{"BufAdd", EVENT_BUFADD},
{"BufCreate", EVENT_BUFADD},
{"BufDelete", EVENT_BUFDELETE},
{"BufEnter", EVENT_BUFENTER},
{"BufFilePost", EVENT_BUFFILEPOST},
{"BufFilePre", EVENT_BUFFILEPRE},
{"BufHidden", EVENT_BUFHIDDEN},
{"BufLeave", EVENT_BUFLEAVE},
{"BufNew", EVENT_BUFNEW},
{"BufNewFile", EVENT_BUFNEWFILE},
{"BufRead", EVENT_BUFREADPOST},
{"BufReadCmd", EVENT_BUFREADCMD},
{"BufReadPost", EVENT_BUFREADPOST},
{"BufReadPre", EVENT_BUFREADPRE},
{"BufUnload", EVENT_BUFUNLOAD},
{"BufWinEnter", EVENT_BUFWINENTER},
{"BufWinLeave", EVENT_BUFWINLEAVE},
{"BufWipeout", EVENT_BUFWIPEOUT},
{"BufWrite", EVENT_BUFWRITEPRE},
{"BufWritePost", EVENT_BUFWRITEPOST},
{"BufWritePre", EVENT_BUFWRITEPRE},
{"BufWriteCmd", EVENT_BUFWRITECMD},
{"CmdwinEnter", EVENT_CMDWINENTER},
{"CmdwinLeave", EVENT_CMDWINLEAVE},
{"ColorScheme", EVENT_COLORSCHEME},
{"CursorHold", EVENT_CURSORHOLD},
{"CursorHoldI", EVENT_CURSORHOLDI},
{"CursorMoved", EVENT_CURSORMOVED},
{"CursorMovedI", EVENT_CURSORMOVEDI},
{"EncodingChanged", EVENT_ENCODINGCHANGED},
{"FileEncoding", EVENT_ENCODINGCHANGED},
{"FileAppendPost", EVENT_FILEAPPENDPOST},
{"FileAppendPre", EVENT_FILEAPPENDPRE},
{"FileAppendCmd", EVENT_FILEAPPENDCMD},
{"FileChangedShell",EVENT_FILECHANGEDSHELL},
{"FileChangedShellPost",EVENT_FILECHANGEDSHELLPOST},
{"FileChangedRO", EVENT_FILECHANGEDRO},
{"FileReadPost", EVENT_FILEREADPOST},
{"FileReadPre", EVENT_FILEREADPRE},
{"FileReadCmd", EVENT_FILEREADCMD},
{"FileType", EVENT_FILETYPE},
{"FileWritePost", EVENT_FILEWRITEPOST},
{"FileWritePre", EVENT_FILEWRITEPRE},
{"FileWriteCmd", EVENT_FILEWRITECMD},
{"FilterReadPost", EVENT_FILTERREADPOST},
{"FilterReadPre", EVENT_FILTERREADPRE},
{"FilterWritePost", EVENT_FILTERWRITEPOST},
{"FilterWritePre", EVENT_FILTERWRITEPRE},
{"FocusGained", EVENT_FOCUSGAINED},
{"FocusLost", EVENT_FOCUSLOST},
{"FuncUndefined", EVENT_FUNCUNDEFINED},
{"GUIEnter", EVENT_GUIENTER},
{"GUIFailed", EVENT_GUIFAILED},
{"InsertChange", EVENT_INSERTCHANGE},
{"InsertEnter", EVENT_INSERTENTER},
{"InsertLeave", EVENT_INSERTLEAVE},
{"MenuPopup", EVENT_MENUPOPUP},
{"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST},
{"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE},
{"RemoteReply", EVENT_REMOTEREPLY},
{"SessionLoadPost", EVENT_SESSIONLOADPOST},
{"ShellCmdPost", EVENT_SHELLCMDPOST},
{"ShellFilterPost", EVENT_SHELLFILTERPOST},
{"SourcePre", EVENT_SOURCEPRE},
{"SourceCmd", EVENT_SOURCECMD},
{"SpellFileMissing",EVENT_SPELLFILEMISSING},
{"StdinReadPost", EVENT_STDINREADPOST},
{"StdinReadPre", EVENT_STDINREADPRE},
{"SwapExists", EVENT_SWAPEXISTS},
{"Syntax", EVENT_SYNTAX},
{"TabEnter", EVENT_TABENTER},
{"TabLeave", EVENT_TABLEAVE},
{"TermChanged", EVENT_TERMCHANGED},
{"TermResponse", EVENT_TERMRESPONSE},
{"User", EVENT_USER},
{"VimEnter", EVENT_VIMENTER},
{"VimLeave", EVENT_VIMLEAVE},
{"VimLeavePre", EVENT_VIMLEAVEPRE},
{"WinEnter", EVENT_WINENTER},
{"WinLeave", EVENT_WINLEAVE},
{"VimResized", EVENT_VIMRESIZED},
{NULL, (event_T)0}
};
static AutoPat *first_autopat[NUM_EVENTS] =
{
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
/*
* struct used to keep status while executing autocommands for an event.
*/
typedef struct AutoPatCmd
{
AutoPat *curpat; /* next AutoPat to examine */
AutoCmd *nextcmd; /* next AutoCmd to execute */
int group; /* group being used */
char_u *fname; /* fname to match with */
char_u *sfname; /* sfname to match with */
char_u *tail; /* tail of fname */
event_T event; /* current event */
int arg_bufnr; /* initially equal to <abuf>, set to zero when
buf is deleted */
struct AutoPatCmd *next; /* chain of active apc-s for auto-invalidation*/
} AutoPatCmd;
static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */
/*
* augroups stores a list of autocmd group names.
*/
static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL};
#define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i])
/*
* The ID of the current group. Group 0 is the default one.
*/
static int current_augroup = AUGROUP_DEFAULT;
static int au_need_clean = FALSE; /* need to delete marked patterns */
static void show_autocmd __ARGS((AutoPat *ap, event_T event));
static void au_remove_pat __ARGS((AutoPat *ap));
static void au_remove_cmds __ARGS((AutoPat *ap));
static void au_cleanup __ARGS((void));
static int au_new_group __ARGS((char_u *name));
static void au_del_group __ARGS((char_u *name));
static event_T event_name2nr __ARGS((char_u *start, char_u **end));
static char_u *event_nr2name __ARGS((event_T event));
static char_u *find_end_event __ARGS((char_u *arg, int have_group));
static int event_ignored __ARGS((event_T event));
static int au_get_grouparg __ARGS((char_u **argp));
static int do_autocmd_event __ARGS((event_T event, char_u *pat, int nested, char_u *cmd, int forceit, int group));
static char_u *getnextac __ARGS((int c, void *cookie, int indent));
static int apply_autocmds_group __ARGS((event_T event, char_u *fname, char_u *fname_io, int force, int group, buf_T *buf, exarg_T *eap));
static void auto_next_pat __ARGS((AutoPatCmd *apc, int stop_at_last));
static event_T last_event;
static int last_group;
/*
* Show the autocommands for one AutoPat.
*/
static void
show_autocmd(ap, event)
AutoPat *ap;
event_T event;
{
AutoCmd *ac;
/* Check for "got_int" (here and at various places below), which is set
* when "q" has been hit for the "--more--" prompt */
if (got_int)
return;
if (ap->pat == NULL) /* pattern has been removed */
return;
msg_putchar('\n');
if (got_int)
return;
if (event != last_event || ap->group != last_group)
{
if (ap->group != AUGROUP_DEFAULT)
{
if (AUGROUP_NAME(ap->group) == NULL)
msg_puts_attr((char_u *)_("--Deleted--"), hl_attr(HLF_E));
else
msg_puts_attr(AUGROUP_NAME(ap->group), hl_attr(HLF_T));
msg_puts((char_u *)" ");
}
msg_puts_attr(event_nr2name(event), hl_attr(HLF_T));
last_event = event;
last_group = ap->group;
msg_putchar('\n');
if (got_int)
return;
}
msg_col = 4;
msg_outtrans(ap->pat);
for (ac = ap->cmds; ac != NULL; ac = ac->next)
{
if (ac->cmd != NULL) /* skip removed commands */
{
if (msg_col >= 14)
msg_putchar('\n');
msg_col = 14;
if (got_int)
return;
msg_outtrans(ac->cmd);
#ifdef FEAT_EVAL
if (p_verbose > 0)
last_set_msg(ac->scriptID);
#endif
if (got_int)
return;
if (ac->next != NULL)
{
msg_putchar('\n');
if (got_int)
return;
}
}
}
}
/*
* Mark an autocommand pattern for deletion.
*/
static void
au_remove_pat(ap)
AutoPat *ap;
{
vim_free(ap->pat);
ap->pat = NULL;
ap->buflocal_nr = -1;
au_need_clean = TRUE;
}
/*
* Mark all commands for a pattern for deletion.
*/
static void
au_remove_cmds(ap)
AutoPat *ap;
{
AutoCmd *ac;
for (ac = ap->cmds; ac != NULL; ac = ac->next)
{
vim_free(ac->cmd);
ac->cmd = NULL;
}
au_need_clean = TRUE;
}
/*
* Cleanup autocommands and patterns that have been deleted.
* This is only done when not executing autocommands.
*/
static void
au_cleanup()
{
AutoPat *ap, **prev_ap;
AutoCmd *ac, **prev_ac;
event_T event;
if (autocmd_busy || !au_need_clean)
return;
/* loop over all events */
for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
event = (event_T)((int)event + 1))
{
/* loop over all autocommand patterns */
prev_ap = &(first_autopat[(int)event]);
for (ap = *prev_ap; ap != NULL; ap = *prev_ap)
{
/* loop over all commands for this pattern */
prev_ac = &(ap->cmds);
for (ac = *prev_ac; ac != NULL; ac = *prev_ac)
{
/* remove the command if the pattern is to be deleted or when
* the command has been marked for deletion */
if (ap->pat == NULL || ac->cmd == NULL)
{
*prev_ac = ac->next;
vim_free(ac->cmd);
vim_free(ac);
}
else
prev_ac = &(ac->next);
}
/* remove the pattern if it has been marked for deletion */
if (ap->pat == NULL)
{
*prev_ap = ap->next;
vim_free(ap->reg_prog);
vim_free(ap);
}
else
prev_ap = &(ap->next);
}
}
au_need_clean = FALSE;
}
/*
* Called when buffer is freed, to remove/invalidate related buffer-local
* autocmds.
*/
void
aubuflocal_remove(buf)
buf_T *buf;
{
AutoPat *ap;
event_T event;
AutoPatCmd *apc;
/* invalidate currently executing autocommands */
for (apc = active_apc_list; apc; apc = apc->next)
if (buf->b_fnum == apc->arg_bufnr)
apc->arg_bufnr = 0;
/* invalidate buflocals looping through events */
for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
event = (event_T)((int)event + 1))
/* loop over all autocommand patterns */
for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next)
if (ap->buflocal_nr == buf->b_fnum)
{
au_remove_pat(ap);
if (p_verbose >= 6)
{
verbose_enter();
smsg((char_u *)
_("auto-removing autocommand: %s <buffer=%d>"),
event_nr2name(event), buf->b_fnum);
verbose_leave();
}
}
au_cleanup();
}
/*
* Add an autocmd group name.
* Return it's ID. Returns AUGROUP_ERROR (< 0) for error.
*/
static int
au_new_group(name)
char_u *name;
{
int i;
i = au_find_group(name);
if (i == AUGROUP_ERROR) /* the group doesn't exist yet, add it */
{
/* First try using a free entry. */
for (i = 0; i < augroups.ga_len; ++i)
if (AUGROUP_NAME(i) == NULL)
break;
if (i == augroups.ga_len && ga_grow(&augroups, 1) == FAIL)
return AUGROUP_ERROR;
AUGROUP_NAME(i) = vim_strsave(name);
if (AUGROUP_NAME(i) == NULL)
return AUGROUP_ERROR;
if (i == augroups.ga_len)
++augroups.ga_len;
}
return i;
}
static void
au_del_group(name)
char_u *name;
{
int i;
i = au_find_group(name);
if (i == AUGROUP_ERROR) /* the group doesn't exist */
EMSG2(_("E367: No such group: \"%s\""), name);
else
{
vim_free(AUGROUP_NAME(i));
AUGROUP_NAME(i) = NULL;
}
}
/*
* Find the ID of an autocmd group name.
* Return it's ID. Returns AUGROUP_ERROR (< 0) for error.
*/
static int
au_find_group(name)
char_u *name;
{
int i;
for (i = 0; i < augroups.ga_len; ++i)
if (AUGROUP_NAME(i) != NULL && STRCMP(AUGROUP_NAME(i), name) == 0)
return i;
return AUGROUP_ERROR;
}
/*
* Return TRUE if augroup "name" exists.
*/
int
au_has_group(name)
char_u *name;
{
return au_find_group(name) != AUGROUP_ERROR;
}
/*
* ":augroup {name}".
*/
void
do_augroup(arg, del_group)
char_u *arg;
int del_group;
{
int i;
if (del_group)
{
if (*arg == NUL)
EMSG(_(e_argreq));
else
au_del_group(arg);
}
else if (STRICMP(arg, "end") == 0) /* ":aug end": back to group 0 */
current_augroup = AUGROUP_DEFAULT;
else if (*arg) /* ":aug xxx": switch to group xxx */
{
i = au_new_group(arg);
if (i != AUGROUP_ERROR)
current_augroup = i;
}
else /* ":aug": list the group names */
{
msg_start();
for (i = 0; i < augroups.ga_len; ++i)
{
if (AUGROUP_NAME(i) != NULL)
{
msg_puts(AUGROUP_NAME(i));
msg_puts((char_u *)" ");
}
}
msg_clr_eos();
msg_end();
}
}
#if defined(EXITFREE) || defined(PROTO)
void
free_all_autocmds()
{
for (current_augroup = -1; current_augroup < augroups.ga_len;
++current_augroup)
do_autocmd((char_u *)"", TRUE);
ga_clear_strings(&augroups);
}
#endif
/*
* Return the event number for event name "start".
* Return NUM_EVENTS if the event name was not found.
* Return a pointer to the next event name in "end".
*/
static event_T
event_name2nr(start, end)
char_u *start;
char_u **end;
{
char_u *p;
int i;
int len;
/* the event name ends with end of line, a blank or a comma */
for (p = start; *p && !vim_iswhite(*p) && *p != ','; ++p)
;
for (i = 0; event_names[i].name != NULL; ++i)
{
len = (int)STRLEN(event_names[i].name);
if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0)
break;
}
if (*p == ',')
++p;
*end = p;
if (event_names[i].name == NULL)
return NUM_EVENTS;
return event_names[i].event;
}
/*
* Return the name for event "event".
*/
static char_u *
event_nr2name(event)
event_T event;
{
int i;
for (i = 0; event_names[i].name != NULL; ++i)
if (event_names[i].event == event)
return (char_u *)event_names[i].name;
return (char_u *)"Unknown";
}
/*
* Scan over the events. "*" stands for all events.
*/
static char_u *
find_end_event(arg, have_group)
char_u *arg;
int have_group; /* TRUE when group name was found */
{
char_u *pat;
char_u *p;
if (*arg == '*')
{
if (arg[1] && !vim_iswhite(arg[1]))
{
EMSG2(_("E215: Illegal character after *: %s"), arg);
return NULL;
}
pat = arg + 1;
}
else
{
for (pat = arg; *pat && !vim_iswhite(*pat); pat = p)
{
if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS)
{
if (have_group)
EMSG2(_("E216: No such event: %s"), pat);
else
EMSG2(_("E216: No such group or event: %s"), pat);
return NULL;
}
}
}
return pat;
}
/*
* Return TRUE if "event" is included in 'eventignore'.
*/
static int
event_ignored(event)
event_T event;
{
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;
}
return FALSE;
}
/*
* Return OK when the contents of p_ei is valid, FAIL otherwise.
*/
int
check_ei()
{
char_u *p = p_ei;
while (*p)
{
if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ','))
{
p += 3;
if (*p == ',')
++p;
}
else if (event_name2nr(p, &p) == NUM_EVENTS)
return FAIL;
}
return OK;
}
# if defined(FEAT_SYN_HL) || defined(PROTO)
/*
* Add "what" to 'eventignore' to skip loading syntax highlighting for every
* buffer loaded into the window. "what" must start with a comma.
* Returns the old value of 'eventignore' in allocated memory.
*/
char_u *
au_event_disable(what)
char *what;
{
char_u *new_ei;
char_u *save_ei;
save_ei = vim_strsave(p_ei);
if (save_ei != NULL)
{
new_ei = vim_strnsave(p_ei, (int)(STRLEN(p_ei) + STRLEN(what)));
if (new_ei != NULL)
{
STRCAT(new_ei, what);
set_string_option_direct((char_u *)"ei", -1, new_ei,
OPT_FREE, SID_NONE);
vim_free(new_ei);
}
}
return save_ei;
}
void
au_event_restore(old_ei)
char_u *old_ei;
{
if (old_ei != NULL)
{
set_string_option_direct((char_u *)"ei", -1, old_ei,
OPT_FREE, SID_NONE);
vim_free(old_ei);
}
}
# endif /* FEAT_SYN_HL */
/*
* do_autocmd() -- implements the :autocmd command. Can be used in the
* following ways:
*
* :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that
* will be automatically executed for <event>
* when editing a file matching <pat>, in
* the current group.
* :autocmd <event> <pat> Show the auto-commands associated with
* <event> and <pat>.
* :autocmd <event> Show the auto-commands associated with
* <event>.
* :autocmd Show all auto-commands.
* :autocmd! <event> <pat> <cmd> Remove all auto-commands associated with
* <event> and <pat>, and add the command
* <cmd>, for the current group.
* :autocmd! <event> <pat> Remove all auto-commands associated with
* <event> and <pat> for the current group.
* :autocmd! <event> Remove all auto-commands associated with
* <event> for the current group.
* :autocmd! Remove ALL auto-commands for the current
* group.
*
* Multiple events and patterns may be given separated by commas. Here are
* some examples:
* :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic
* :autocmd bufleave * set tw=79 nosmartindent ic infercase
*
* :autocmd * *.c show all autocommands for *.c files.
*
* Mostly a {group} argument can optionally appear before <event>.
*/
void
do_autocmd(arg, forceit)
char_u *arg;
int forceit;
{
char_u *pat;
char_u *envpat = NULL;
char_u *cmd;
event_T event;
int need_free = FALSE;
int nested = FALSE;
int group;
/*
* Check for a legal group name. If not, use AUGROUP_ALL.
*/
group = au_get_grouparg(&arg);
if (arg == NULL) /* out of memory */
return;
/*
* Scan over the events.
* If we find an illegal name, return here, don't do anything.
*/
pat = find_end_event(arg, group != AUGROUP_ALL);
if (pat == NULL)
return;
/*
* Scan over the pattern. Put a NUL at the end.
*/
pat = skipwhite(pat);
cmd = pat;
while (*cmd && (!vim_iswhite(*cmd) || cmd[-1] == '\\'))
cmd++;
if (*cmd)
*cmd++ = NUL;
/* Expand environment variables in the pattern. Set 'shellslash', we want
* forward slashes here. */
if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL)
{
#ifdef BACKSLASH_IN_FILENAME
int p_ssl_save = p_ssl;
p_ssl = TRUE;
#endif
envpat = expand_env_save(pat);
#ifdef BACKSLASH_IN_FILENAME
p_ssl = p_ssl_save;
#endif
if (envpat != NULL)
pat = envpat;
}
/*
* Check for "nested" flag.
*/
cmd = skipwhite(cmd);
if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 && vim_iswhite(cmd[6]))
{
nested = TRUE;
cmd = skipwhite(cmd + 6);
}
/*
* Find the start of the commands.
* Expand <sfile> in it.
*/
if (*cmd != NUL)
{
cmd = expand_sfile(cmd);
if (cmd == NULL) /* some error */
return;
need_free = TRUE;
}
/*
* Print header when showing autocommands.
*/
if (!forceit && *cmd == NUL)
{
/* Highlight title */
MSG_PUTS_TITLE(_("\n--- Auto-Commands ---"));
}
/*
* Loop over the events.
*/
last_event = (event_T)-1; /* for listing the event name */
last_group = AUGROUP_ERROR; /* for listing the group name */
if (*arg == '*' || *arg == NUL)
{
for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
event = (event_T)((int)event + 1))
if (do_autocmd_event(event, pat,
nested, cmd, forceit, group) == FAIL)
break;
}
else
{
while (*arg && !vim_iswhite(*arg))
if (do_autocmd_event(event_name2nr(arg, &arg), pat,
nested, cmd, forceit, group) == FAIL)
break;
}
if (need_free)
vim_free(cmd);
vim_free(envpat);
}
/*
* Find the group ID in a ":autocmd" or ":doautocmd" argument.
* The "argp" argument is advanced to the following argument.
*
* Returns the group ID, AUGROUP_ERROR for error (out of memory).
*/
static int
au_get_grouparg(argp)
char_u **argp;
{
char_u *group_name;
char_u *p;
char_u *arg = *argp;
int group = AUGROUP_ALL;
p = skiptowhite(arg);
if (p > arg)
{
group_name = vim_strnsave(arg, (int)(p - arg));
if (group_name == NULL) /* out of memory */
return AUGROUP_ERROR;
group = au_find_group(group_name);
if (group == AUGROUP_ERROR)
group = AUGROUP_ALL; /* no match, use all groups */
else
*argp = skipwhite(p); /* match, skip over group name */
vim_free(group_name);
}
return group;
}
/*
* do_autocmd() for one event.
* If *pat == NUL do for all patterns.
* If *cmd == NUL show entries.
* If forceit == TRUE delete entries.
* If group is not AUGROUP_ALL, only use this group.
*/
static int
do_autocmd_event(event, pat, nested, cmd, forceit, group)
event_T event;
char_u *pat;
int nested;
char_u *cmd;
int forceit;
int group;
{
AutoPat *ap;
AutoPat **prev_ap;
AutoCmd *ac;
AutoCmd **prev_ac;
int brace_level;
char_u *endpat;
int findgroup;
int allgroups;
int patlen;
int is_buflocal;
int buflocal_nr;
char_u buflocal_pat[25]; /* for "<buffer=X>" */
if (group == AUGROUP_ALL)
findgroup = current_augroup;
else
findgroup = group;
allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL);
/*
* Show or delete all patterns for an event.
*/
if (*pat == NUL)
{
for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next)
{
if (forceit) /* delete the AutoPat, if it's in the current group */
{
if (ap->group == findgroup)
au_remove_pat(ap);
}
else if (group == AUGROUP_ALL || ap->group == group)
show_autocmd(ap, event);
}
}
/*
* Loop through all the specified patterns.
*/
for ( ; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat))
{
/*
* Find end of the pattern.
* Watch out for a comma in braces, like "*.\{obj,o\}".
*/
brace_level = 0;
for (endpat = pat; *endpat && (*endpat != ',' || brace_level
|| endpat[-1] == '\\'); ++endpat)
{
if (*endpat == '{')
brace_level++;
else if (*endpat == '}')
brace_level--;
}
if (pat == endpat) /* ignore single comma */
continue;
patlen = (int)(endpat - pat);
/*
* detect special <buflocal[=X]> buffer-local patterns
*/
is_buflocal = FALSE;
buflocal_nr = 0;
if (patlen >= 7 && STRNCMP(pat, "<buffer", 7) == 0
&& pat[patlen - 1] == '>')
{
/* Error will be printed only for addition. printing and removing
* will proceed silently. */
is_buflocal = TRUE;
if (patlen == 8)
buflocal_nr = curbuf->b_fnum;
else if (patlen > 9 && pat[7] == '=')
{
/* <buffer=abuf> */
if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13))
buflocal_nr = autocmd_bufnr;
/* <buffer=123> */
else if (skipdigits(pat + 8) == pat + patlen - 1)
buflocal_nr = atoi((char *)pat + 8);
}
}
if (is_buflocal)
{
/* normalize pat into standard "<buffer>#N" form */
sprintf((char *)buflocal_pat, "<buffer=%d>", buflocal_nr);
pat = buflocal_pat; /* can modify pat and patlen */
patlen = (int)STRLEN(buflocal_pat); /* but not endpat */
}
/*
* Find AutoPat entries with this pattern.
*/
prev_ap = &first_autopat[(int)event];
while ((ap = *prev_ap) != NULL)
{
if (ap->pat != NULL)
{
/* Accept a pattern when:
* - a group was specified and it's that group, or a group was
* not specified and it's the current group, or a group was
* not specified and we are listing
* - the length of the pattern matches
* - the pattern matches.
* For <buffer[=X]>, this condition works because we normalize
* all buffer-local patterns.
*/
if ((allgroups || ap->group == findgroup)
&& ap->patlen == patlen
&& STRNCMP(pat, ap->pat, patlen) == 0)
{
/*
* Remove existing autocommands.
* If adding any new autocmd's for this AutoPat, don't
* delete the pattern from the autopat list, append to
* this list.
*/
if (forceit)
{
if (*cmd != NUL && ap->next == NULL)
{
au_remove_cmds(ap);
break;
}
au_remove_pat(ap);
}
/*
* Show autocmd's for this autopat, or buflocals <buffer=X>
*/
else if (*cmd == NUL)
show_autocmd(ap, event);
/*
* Add autocmd to this autopat, if it's the last one.
*/
else if (ap->next == NULL)
break;
}
}
prev_ap = &ap->next;
}
/*
* Add a new command.
*/
if (*cmd != NUL)
{
/*
* If the pattern we want to add a command to does appear at the
* end of the list (or not is not in the list at all), add the
* pattern at the end of the list.
*/
if (ap == NULL)
{
/* refuse to add buffer-local ap if buffer number is invalid */
if (is_buflocal && (buflocal_nr == 0
|| buflist_findnr(buflocal_nr) == NULL))
{
EMSGN(_("E680: <buffer=%d>: invalid buffer number "),
buflocal_nr);
return FAIL;
}
ap = (AutoPat *)alloc((unsigned)sizeof(AutoPat));
if (ap == NULL)
return FAIL;
ap->pat = vim_strnsave(pat, patlen);
ap->patlen = patlen;
if (ap->pat == NULL)
{
vim_free(ap);
return FAIL;
}
if (is_buflocal)
{
ap->buflocal_nr = buflocal_nr;
ap->reg_prog = NULL;
}
else
{
char_u *reg_pat;
ap->buflocal_nr = 0;
reg_pat = file_pat_to_reg_pat(pat, endpat,
&ap->allow_dirs, TRUE);
if (reg_pat != NULL)
ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
vim_free(reg_pat);
if (reg_pat == NULL || ap->reg_prog == NULL)
{
vim_free(ap->pat);
vim_free(ap);
return FAIL;
}
}
ap->cmds = NULL;
*prev_ap = ap;
ap->next = NULL;
if (group == AUGROUP_ALL)
ap->group = current_augroup;
else
ap->group = group;
}
/*
* Add the autocmd at the end of the AutoCmd list.
*/
prev_ac = &(ap->cmds);
while ((ac = *prev_ac) != NULL)
prev_ac = &ac->next;
ac = (AutoCmd *)alloc((unsigned)sizeof(AutoCmd));
if (ac == NULL)
return FAIL;
ac->cmd = vim_strsave(cmd);
#ifdef FEAT_EVAL
ac->scriptID = current_SID;
#endif
if (ac->cmd == NULL)
{
vim_free(ac);
return FAIL;
}
ac->next = NULL;
*prev_ac = ac;
ac->nested = nested;
}
}
au_cleanup(); /* may really delete removed patterns/commands now */
return OK;
}
/*
* Implementation of ":doautocmd [group] event [fname]".
* Return OK for success, FAIL for failure;
*/
int
do_doautocmd(arg, do_msg)
char_u *arg;
int do_msg; /* give message for no matching autocmds? */
{
char_u *fname;
int nothing_done = TRUE;
int group;
/*
* Check for a legal group name. If not, use AUGROUP_ALL.
*/
group = au_get_grouparg(&arg);
if (arg == NULL) /* out of memory */
return FAIL;
if (*arg == '*')
{
EMSG(_("E217: Can't execute autocommands for ALL events"));
return FAIL;
}
/*
* Scan over the events.
* If we find an illegal name, return here, don't do anything.
*/
fname = find_end_event(arg, group != AUGROUP_ALL);
if (fname == NULL)
return FAIL;
fname = skipwhite(fname);
/*
* Loop over the events.
*/
while (*arg && !vim_iswhite(*arg))
if (apply_autocmds_group(event_name2nr(arg, &arg),
fname, NULL, TRUE, group, curbuf, NULL))
nothing_done = FALSE;
if (nothing_done && do_msg)
MSG(_("No matching autocommands"));
#ifdef FEAT_EVAL
return aborting() ? FAIL : OK;
#else
return OK;
#endif
}
/*
* ":doautoall": execute autocommands for each loaded buffer.
*/
void
ex_doautoall(eap)
exarg_T *eap;
{
int retval;
aco_save_T aco;
buf_T *buf;
/*
* This is a bit tricky: For some commands curwin->w_buffer needs to be
* equal to curbuf, but for some buffers there may not be a window.
* So we change the buffer for the current window for a moment. This
* gives problems when the autocommands make changes to the list of
* buffers or windows...
*/
for (buf = firstbuf; buf != NULL; buf = buf->b_next)
{
if (curbuf->b_ml.ml_mfp != NULL)
{
/* find a window for this buffer and save some values */
aucmd_prepbuf(&aco, buf);
/* execute the autocommands for this buffer */
retval = do_doautocmd(eap->arg, FALSE);
/* Execute the modeline settings, but don't set window-local
* options if we are using the current window for another buffer. */
do_modelines(aco.save_curwin == NULL ? OPT_NOWIN : 0);
/* restore the current window */
aucmd_restbuf(&aco);
/* stop if there is some error or buffer was deleted */
if (retval == FAIL || !buf_valid(buf))
break;
}
}
check_cursor(); /* just in case lines got deleted */
}
/*
* Prepare for executing autocommands for (hidden) buffer "buf".
* Search a window for the current buffer. Save the cursor position and
* screen offset.
* Set "curbuf" and "curwin" to match "buf".
* When FEAT_AUTOCMD is not defined another version is used, see below.
*/
void
aucmd_prepbuf(aco, buf)
aco_save_T *aco; /* structure to save values in */
buf_T *buf; /* new curbuf */
{
win_T *win;
aco->new_curbuf = buf;
/* Find a window that is for the new buffer */
if (buf == curbuf) /* be quick when buf is curbuf */
win = curwin;
else
#ifdef FEAT_WINDOWS
for (win = firstwin; win != NULL; win = win->w_next)
if (win->w_buffer == buf)
break;
#else
win = NULL;
#endif
/*
* Prefer to use an existing window for the buffer, it has the least side
* effects (esp. if "buf" is curbuf).
* Otherwise, use curwin for "buf". It might make some items in the
* window invalid. At least save the cursor and topline.
*/
if (win != NULL)
{
/* there is a window for "buf", make it the curwin */
aco->save_curwin = curwin;
curwin = win;
aco->save_buf = win->w_buffer;
aco->new_curwin = win;
}
else
{
/* there is no window for "buf", use curwin */
aco->save_curwin = NULL;
aco->save_buf = curbuf;
--curbuf->b_nwindows;
curwin->w_buffer = buf;
++buf->b_nwindows;
/* save cursor and topline, set them to safe values */
aco->save_cursor = curwin->w_cursor;
curwin->w_cursor.lnum = 1;
curwin->w_cursor.col = 0;
aco->save_topline = curwin->w_topline;
curwin->w_topline = 1;
#ifdef FEAT_DIFF
aco->save_topfill = curwin->w_topfill;
curwin->w_topfill = 0;
#endif
}
curbuf = buf;
}
/*
* Cleanup after executing autocommands for a (hidden) buffer.
* Restore the window as it was (if possible).
* When FEAT_AUTOCMD is not defined another version is used, see below.
*/
void
aucmd_restbuf(aco)
aco_save_T *aco; /* structure holding saved values */
{
if (aco->save_curwin != NULL)
{
/* restore curwin */
#ifdef FEAT_WINDOWS
if (win_valid(aco->save_curwin))
#endif
{
/* restore the buffer which was previously edited by curwin, if
* it's still the same window and it's valid */
if (curwin == aco->new_curwin
&& buf_valid(aco->save_buf)
&& aco->save_buf->b_ml.ml_mfp != NULL)
{
--curbuf->b_nwindows;
curbuf = aco->save_buf;
curwin->w_buffer = curbuf;
++curbuf->b_nwindows;
}
curwin = aco->save_curwin;
curbuf = curwin->w_buffer;
}
}
else
{
/* restore buffer for curwin if it still exists and is loaded */
if (buf_valid(aco->save_buf) && aco->save_buf->b_ml.ml_mfp != NULL)
{
--curbuf->b_nwindows;
curbuf = aco->save_buf;
curwin->w_buffer = curbuf;
++curbuf->b_nwindows;
curwin->w_cursor = aco->save_cursor;
check_cursor();
/* check topline < line_count, in case lines got deleted */
if (aco->save_topline <= curbuf->b_ml.ml_line_count)
{
curwin->w_topline = aco->save_topline;
#ifdef FEAT_DIFF
curwin->w_topfill = aco->save_topfill;
#endif
}
else
{
curwin->w_topline = curbuf->b_ml.ml_line_count;
#ifdef FEAT_DIFF
curwin->w_topfill = 0;
#endif
}
}
}
}
static int autocmd_nested = FALSE;
/*
* Execute autocommands for "event" and file name "fname".
* Return TRUE if some commands were executed.
*/
int
apply_autocmds(event, fname, fname_io, force, buf)
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> */
{
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, fname, fname_io, force, buf, eap)
event_T event;
char_u *fname;
char_u *fname_io;
int 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, fname, fname_io, force, buf, 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 */
{
int did_cmd;
#ifdef FEAT_EVAL
if (should_abort(*retval))
return FALSE;
#endif
did_cmd = apply_autocmds_group(event, fname, fname_io, force,
AUGROUP_ALL, buf, NULL);
if (did_cmd
#ifdef FEAT_EVAL
&& aborting()
#endif
)
*retval = FAIL;
return did_cmd;
}
/*
* Return TRUE when there is a CursorHold autocommand defined.
*/
int
has_cursorhold()
{
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()
{
int state;
if (!did_cursorhold && has_cursorhold() && !Recording
#ifdef FEAT_INS_EXPAND
&& !ins_compl_active()
#endif
)
{
state = get_real_state();
if (state == NORMAL_BUSY || (state & INSERT) != 0)
return TRUE;
}
return FALSE;
}
/*
* Return TRUE when there is a CursorMoved autocommand defined.
*/
int
has_cursormoved()
{
return (first_autopat[(int)EVENT_CURSORMOVED] != NULL);
}
/*
* Return TRUE when there is a CursorMovedI autocommand defined.
*/
int
has_cursormovedI()
{
return (first_autopat[(int)EVENT_CURSORMOVEDI] != NULL);
}
static int
apply_autocmds_group(event, fname, fname_io, force, group, buf, eap)
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 */
{
char_u *sfname = NULL; /* short file name */
char_u *tail;
int save_changed;
buf_T *old_curbuf;
int retval = FALSE;
char_u *save_sourcing_name;
linenr_T save_sourcing_lnum;
char_u *save_autocmd_fname;
int save_autocmd_bufnr;
char_u *save_autocmd_match;
int save_autocmd_busy;
int save_autocmd_nested;
static int nesting = 0;
AutoPatCmd patcmd;
AutoPat *ap;
#ifdef FEAT_EVAL
scid_T save_current_SID;
void *save_funccalp;
char_u *save_cmdarg;
long save_cmdbang;
#endif
static int filechangeshell_busy = FALSE;
#ifdef FEAT_PROFILE
proftime_T wait_time;
#endif
/*
* Quickly return if there are no autocommands for this event or
* autocommands are blocked.
*/
if (first_autopat[(int)event] == NULL || autocmd_block > 0)
goto BYPASS_AU;
/*
* When autocommands are busy, new autocommands are only executed when
* explicitly enabled with the "nested" flag.
*/
if (autocmd_busy && !(force || autocmd_nested))
goto BYPASS_AU;
#ifdef FEAT_EVAL
/*
* Quickly return when immediately aborting on error, or when an interrupt
* occurred or an exception was thrown but not caught.
*/
if (aborting())
goto BYPASS_AU;
#endif
/*
* FileChangedShell never nests, because it can create an endless loop.
*/
if (filechangeshell_busy && (event == EVENT_FILECHANGEDSHELL
|| event == EVENT_FILECHANGEDSHELLPOST))
goto BYPASS_AU;
/*
* Ignore events in 'eventignore'.
*/
if (event_ignored(event))
goto BYPASS_AU;
/*
* Allow nesting of autocommands, but restrict the depth, because it's
* possible to create an endless loop.
*/
if (nesting == 10)
{
EMSG(_("E218: autocommand nesting too deep"));
goto BYPASS_AU;
}
/*
* Check if these autocommands are disabled. Used when doing ":all" or
* ":ball".
*/
if ( (autocmd_no_enter
&& (event == EVENT_WINENTER || event == EVENT_BUFENTER))
|| (autocmd_no_leave
&& (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE)))
goto BYPASS_AU;
/*
* Save the autocmd_* variables and info about the current buffer.
*/
save_autocmd_fname = autocmd_fname;
save_autocmd_bufnr = autocmd_bufnr;
save_autocmd_match = autocmd_match;
save_autocmd_busy = autocmd_busy;
save_autocmd_nested = autocmd_nested;
save_changed = curbuf->b_changed;
old_curbuf = curbuf;
/*
* Set the file name to be used for <afile>.
*/
if (fname_io == NULL)
{
if (fname != NULL && *fname != NUL)
autocmd_fname = fname;
else if (buf != NULL)
autocmd_fname = buf->b_fname;
else
autocmd_fname = NULL;
}
else
autocmd_fname = fname_io;
/*
* Set the buffer number to be used for <abuf>.
*/
if (buf == NULL)
autocmd_bufnr = 0;
else
autocmd_bufnr = buf->b_fnum;
/*
* When the file name is NULL or empty, use the file name of buffer "buf".
* Always use the full path of the file name to match with, in case
* "allow_dirs" is set.
*/
if (fname == NULL || *fname == NUL)
{
if (buf == NULL)
fname = NULL;
else
{
#ifdef FEAT_SYN_HL
if (event == EVENT_SYNTAX)
fname = buf->b_p_syn;
else
#endif
if (event == EVENT_FILETYPE)
fname = buf->b_p_ft;
else
{
if (buf->b_sfname != NULL)
sfname = vim_strsave(buf->b_sfname);
fname = buf->b_ffname;
}
}
if (fname == NULL)
fname = (char_u *)"";
fname = vim_strsave(fname); /* make a copy, so we can change it */
}
else
{
sfname = vim_strsave(fname);
/* Don't try expanding FileType, Syntax, WindowID or QuickFixCmd* */
if (event == EVENT_FILETYPE
|| event == EVENT_SYNTAX
|| event == EVENT_REMOTEREPLY
|| event == EVENT_SPELLFILEMISSING
|| event == EVENT_QUICKFIXCMDPRE
|| event == EVENT_QUICKFIXCMDPOST)
fname = vim_strsave(fname);
else
fname = FullName_save(fname, FALSE);
}
if (fname == NULL) /* out of memory */
{
vim_free(sfname);
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)
forward_slash(sfname);
forward_slash(fname);
#endif
#ifdef VMS
/* remove version for correct match */
if (sfname != NULL)
vms_remove_version(sfname);
vms_remove_version(fname);
#endif
/*
* Set the name to be used for <amatch>.
*/
autocmd_match = fname;
/* Don't redraw while doing auto commands. */
++RedrawingDisabled;
save_sourcing_name = sourcing_name;
sourcing_name = NULL; /* don't free this one */
save_sourcing_lnum = sourcing_lnum;
sourcing_lnum = 0; /* no line number here */
#ifdef FEAT_EVAL
save_current_SID = current_SID;
# ifdef FEAT_PROFILE
if (do_profiling == PROF_YES)
prof_child_enter(&wait_time); /* doesn't count for the caller itself */
# endif
/* Don't use local function variables, if called from a function */
save_funccalp = save_funccal();
#endif
/*
* When starting to execute autocommands, save the search patterns.
*/
if (!autocmd_busy)
{
save_search_patterns();
saveRedobuff();
did_filetype = keep_filetype;
}
/*
* Note that we are applying autocmds. Some commands need to know.
*/
autocmd_busy = TRUE;
filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL);
++nesting; /* see matching decrement below */
/* Remember that FileType was triggered. Used for did_filetype(). */
if (event == EVENT_FILETYPE)
did_filetype = TRUE;
tail = gettail(fname);
/* Find first autocommand that matches */
patcmd.curpat = first_autopat[(int)event];
patcmd.nextcmd = NULL;
patcmd.group = group;
patcmd.fname = fname;
patcmd.sfname = sfname;
patcmd.tail = tail;
patcmd.event = event;
patcmd.arg_bufnr = autocmd_bufnr;
patcmd.next = NULL;
auto_next_pat(&patcmd, FALSE);
/* found one, start executing the autocommands */
if (patcmd.curpat != NULL)
{
/* add to active_apc_list */
patcmd.next = active_apc_list;
active_apc_list = &patcmd;
#ifdef FEAT_EVAL
/* set v:cmdarg (only when there is a matching pattern) */
save_cmdbang = get_vim_var_nr(VV_CMDBANG);
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 */
#endif
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);
#ifdef FEAT_EVAL
if (eap != NULL)
{
(void)set_cmdarg(NULL, save_cmdarg);
set_vim_var_nr(VV_CMDBANG, save_cmdbang);
}
#endif
/* delete from active_apc_list */
if (active_apc_list == &patcmd) /* just in case */
active_apc_list = patcmd.next;
}
--RedrawingDisabled;
autocmd_busy = save_autocmd_busy;
filechangeshell_busy = FALSE;
autocmd_nested = save_autocmd_nested;
vim_free(sourcing_name);
sourcing_name = save_sourcing_name;
sourcing_lnum = save_sourcing_lnum;
autocmd_fname = save_autocmd_fname;
autocmd_bufnr = save_autocmd_bufnr;
autocmd_match = save_autocmd_match;
#ifdef FEAT_EVAL
current_SID = save_current_SID;
restore_funccal(save_funccalp);
# ifdef FEAT_PROFILE
if (do_profiling == PROF_YES)
prof_child_exit(&wait_time);
# endif
#endif
vim_free(fname);
vim_free(sfname);
--nesting; /* see matching increment above */
/*
* When stopping to execute autocommands, restore the search patterns and
* the redo buffer.
*/
if (!autocmd_busy)
{
restore_search_patterns();
restoreRedobuff();
did_filetype = FALSE;
}
/*
* Some events don't set or reset the Changed flag.
* Check if still in the same buffer!
*/
if (curbuf == old_curbuf
&& (event == EVENT_BUFREADPOST
|| event == EVENT_BUFWRITEPOST
|| event == EVENT_FILEAPPENDPOST
|| event == EVENT_VIMLEAVE
|| event == EVENT_VIMLEAVEPRE))
{
#ifdef FEAT_TITLE
if (curbuf->b_changed != save_changed)
need_maketitle = TRUE;
#endif
curbuf->b_changed = save_changed;
}
au_cleanup(); /* may really delete removed patterns/commands now */
BYPASS_AU:
/* When wiping out a buffer make sure all its buffer-local autocommands
* are deleted. */
if (event == EVENT_BUFWIPEOUT && buf != NULL)
aubuflocal_remove(buf);
return retval;
}
/*
* Find next autocommand pattern that matches.
*/
static void
auto_next_pat(apc, stop_at_last)
AutoPatCmd *apc;
int stop_at_last; /* stop when 'last' flag is set */
{
AutoPat *ap;
AutoCmd *cp;
char_u *name;
char *s;
vim_free(sourcing_name);
sourcing_name = NULL;
for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next)
{
apc->curpat = NULL;
/* only use a pattern when it has not been removed, has commands and
* the group matches. For buffer-local autocommands only check the
* buffer number. */
if (ap->pat != NULL && ap->cmds != NULL
&& (apc->group == AUGROUP_ALL || apc->group == ap->group))
{
/* execution-condition */
if (ap->buflocal_nr == 0
? (match_file_pat(NULL, ap->reg_prog, apc->fname,
apc->sfname, apc->tail, ap->allow_dirs))
: ap->buflocal_nr == apc->arg_bufnr)
{
name = event_nr2name(apc->event);
s = _("%s Auto commands for \"%s\"");
sourcing_name = alloc((unsigned)(STRLEN(s)
+ STRLEN(name) + ap->patlen + 1));
if (sourcing_name != NULL)
{
sprintf((char *)sourcing_name, s,
(char *)name, (char *)ap->pat);
if (p_verbose >= 8)
{
verbose_enter();
smsg((char_u *)_("Executing %s"), sourcing_name);
verbose_leave();
}
}
apc->curpat = ap;
apc->nextcmd = ap->cmds;
/* mark last command */
for (cp = ap->cmds; cp->next != NULL; cp = cp->next)
cp->last = FALSE;
cp->last = TRUE;
}
line_breakcheck();
if (apc->curpat != NULL) /* found a match */
break;
}
if (stop_at_last && ap->last)
break;
}
}
/*
* Get next autocommand command.
* Called by do_cmdline() to get the next line for ":if".
* Returns allocated string, or NULL for end of autocommands.
*/
/* ARGSUSED */
static char_u *
getnextac(c, cookie, indent)
int c; /* not used */
void *cookie;
int indent; /* not used */
{
AutoPatCmd *acp = (AutoPatCmd *)cookie;
char_u *retval;
AutoCmd *ac;
/* Can be called again after returning the last line. */
if (acp->curpat == NULL)
return NULL;
/* repeat until we find an autocommand to execute */
for (;;)
{
/* skip removed commands */
while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL)
if (acp->nextcmd->last)
acp->nextcmd = NULL;
else
acp->nextcmd = acp->nextcmd->next;
if (acp->nextcmd != NULL)
break;
/* at end of commands, find next pattern that matches */
if (acp->curpat->last)
acp->curpat = NULL;
else
acp->curpat = acp->curpat->next;
if (acp->curpat != NULL)
auto_next_pat(acp, TRUE);
if (acp->curpat == NULL)
return NULL;
}
ac = acp->nextcmd;
if (p_verbose >= 9)
{
verbose_enter_scroll();
smsg((char_u *)_("autocommand %s"), ac->cmd);
msg_puts((char_u *)"\n"); /* don't overwrite this either */
verbose_leave_scroll();
}
retval = vim_strsave(ac->cmd);
autocmd_nested = ac->nested;
#ifdef FEAT_EVAL
current_SID = ac->scriptID;
#endif
if (ac->last)
acp->nextcmd = NULL;
else
acp->nextcmd = ac->next;
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, sfname, buf)
event_T event;
char_u *sfname;
buf_T *buf;
{
AutoPat *ap;
char_u *fname;
char_u *tail = gettail(sfname);
int retval = FALSE;
fname = FullName_save(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.
*/
sfname = vim_strsave(sfname);
if (sfname != NULL)
forward_slash(sfname);
forward_slash(fname);
#endif
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;
break;
}
vim_free(fname);
#ifdef BACKSLASH_IN_FILENAME
vim_free(sfname);
#endif
return retval;
}
#if defined(FEAT_CMDL_COMPL) || defined(PROTO)
/*
* Function given to ExpandGeneric() to obtain the list of autocommand group
* names.
*/
/*ARGSUSED*/
char_u *
get_augroup_name(xp, idx)
expand_T *xp;
int idx;
{
if (idx == augroups.ga_len) /* add "END" add the end */
return (char_u *)"END";
if (idx >= augroups.ga_len) /* end of list */
return NULL;
if (AUGROUP_NAME(idx) == NULL) /* skip deleted entries */
return (char_u *)"";
return AUGROUP_NAME(idx); /* return a name */
}
static int include_groups = FALSE;
char_u *
set_context_in_autocmd(xp, arg, doautocmd)
expand_T *xp;
char_u *arg;
int doautocmd; /* TRUE for :doautocmd, FALSE for :autocmd */
{
char_u *p;
int group;
/* check for a group name, skip it if present */
include_groups = FALSE;
p = arg;
group = au_get_grouparg(&arg);
if (group == AUGROUP_ERROR)
return NULL;
/* If there only is a group name that's what we expand. */
if (*arg == NUL && group != AUGROUP_ALL && !vim_iswhite(arg[-1]))
{
arg = p;
group = AUGROUP_ALL;
}
/* skip over event name */
for (p = arg; *p != NUL && !vim_iswhite(*p); ++p)
if (*p == ',')
arg = p + 1;
if (*p == NUL)
{
if (group == AUGROUP_ALL)
include_groups = TRUE;
xp->xp_context = EXPAND_EVENTS; /* expand event name */
xp->xp_pattern = arg;
return NULL;
}
/* skip over pattern */
arg = skipwhite(p);
while (*arg && (!vim_iswhite(*arg) || arg[-1] == '\\'))
arg++;
if (*arg)
return arg; /* expand (next) command */
if (doautocmd)
xp->xp_context = EXPAND_FILES; /* expand file names */
else
xp->xp_context = EXPAND_NOTHING; /* pattern is not expanded */
return NULL;
}
/*
* Function given to ExpandGeneric() to obtain the list of event names.
*/
/*ARGSUSED*/
char_u *
get_event_name(xp, idx)
expand_T *xp;
int idx;
{
if (idx < augroups.ga_len) /* First list group names, if wanted */
{
if (!include_groups || AUGROUP_NAME(idx) == NULL)
return (char_u *)""; /* skip deleted entries */
return AUGROUP_NAME(idx); /* return a name */
}
return (char_u *)event_names[idx - augroups.ga_len].name;
}
#endif /* FEAT_CMDL_COMPL */
/*
* Return TRUE if autocmd is supported.
*/
int
autocmd_supported(name)
char_u *name;
{
char_u *p;
return (event_name2nr(name, &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(arg)
char_u *arg;
{
char_u *arg_save;
char_u *pattern = NULL;
char_u *event_name;
char_u *p;
event_T event;
AutoPat *ap;
buf_T *buflocal_buf = NULL;
int group;
int retval = FALSE;
/* Make a copy so that we can change the '#' chars to a NUL. */
arg_save = vim_strsave(arg);
if (arg_save == NULL)
return FALSE;
p = vim_strchr(arg_save, '#');
if (p != NULL)
*p++ = NUL;
/* First, look for an autocmd group name */
group = au_find_group(arg_save);
if (group == AUGROUP_ERROR)
{
/* Didn't match a group name, assume the first argument is an event. */
group = AUGROUP_ALL;
event_name = arg_save;
}
else
{
if (p == NULL)
{
/* "Group": group name is present and it's recognized */
retval = TRUE;
goto theend;
}
/* Must be "Group#Event" or "Group#Event#pat". */
event_name = p;
p = vim_strchr(event_name, '#');
if (p != NULL)
*p++ = NUL; /* "Group#Event#pat" */
}
pattern = p; /* "pattern" is NULL when there is no pattern */
/* find the index (enum) for the event name */
event = event_name2nr(event_name, &p);
/* return FALSE if the event name is not recognized */
if (event == NUM_EVENTS)
goto theend;
/* Find the first autocommand for this event.
* If there isn't any, return FALSE;
* If there is one and no pattern given, return TRUE; */
ap = first_autopat[(int)event];
if (ap == NULL)
goto theend;
if (pattern == NULL)
{
retval = TRUE;
goto theend;
}
/* if pattern is "<buffer>", special handling is needed which uses curbuf */
/* for pattern "<buffer=N>, fnamecmp() will work fine */
if (STRICMP(pattern, "<buffer>") == 0)
buflocal_buf = curbuf;
/* Check if there is an autocommand with the given pattern. */
for ( ; ap != NULL; ap = ap->next)
/* only use a pattern when it has not been removed and has commands. */
/* For buffer-local autocommands, fnamecmp() works fine. */
if (ap->pat != NULL && ap->cmds != NULL
&& (group == AUGROUP_ALL || ap->group == group)
&& (buflocal_buf == NULL
? fnamecmp(ap->pat, pattern) == 0
: ap->buflocal_nr == buflocal_buf->b_fnum))
{
retval = TRUE;
break;
}
theend:
vim_free(arg_save);
return retval;
}
#else /* FEAT_AUTOCMD */
/*
* Prepare for executing commands for (hidden) buffer "buf".
* This is the non-autocommand version, it simply saves "curbuf" and sets
* "curbuf" and "curwin" to match "buf".
*/
void
aucmd_prepbuf(aco, buf)
aco_save_T *aco; /* structure to save values in */
buf_T *buf; /* new curbuf */
{
aco->save_buf = buf;
curbuf = buf;
curwin->w_buffer = buf;
}
/*
* Restore after executing commands for a (hidden) buffer.
* This is the non-autocommand version.
*/
void
aucmd_restbuf(aco)
aco_save_T *aco; /* structure holding saved values */
{
curbuf = aco->save_buf;
curwin->w_buffer = curbuf;
}
#endif /* FEAT_AUTOCMD */
#if defined(FEAT_AUTOCMD) || defined(FEAT_WILDIGN) || defined(PROTO)
/*
* Try matching a filename with a "pattern" ("prog" is NULL), or use the
* precompiled regprog "prog" ("pattern" is NULL). That avoids calling
* vim_regcomp() often.
* Used for autocommands and 'wildignore'.
* Returns TRUE if there is a match, FALSE otherwise.
*/
int
match_file_pat(pattern, prog, fname, sfname, tail, allow_dirs)
char_u *pattern; /* pattern to match with */
regprog_T *prog; /* pre-compiled regprog or NULL */
char_u *fname; /* full path of file name */
char_u *sfname; /* short file name or NULL */
char_u *tail; /* tail of path */
int allow_dirs; /* allow matching with dir */
{
regmatch_T regmatch;
int result = FALSE;
#ifdef FEAT_OSFILETYPE
int no_pattern = FALSE; /* TRUE if check is filetype only */
char_u *type_start;
char_u c;
int match = FALSE;
#endif
#ifdef CASE_INSENSITIVE_FILENAME
regmatch.rm_ic = TRUE; /* Always ignore case */
#else
regmatch.rm_ic = FALSE; /* Don't ever ignore case */
#endif
#ifdef FEAT_OSFILETYPE
if (*pattern == '<')
{
/* There is a filetype condition specified with this pattern.
* Check the filetype matches first. If not, don't bother with the
* pattern (set regprog to NULL).
* Always use magic for the regexp.
*/
for (type_start = pattern + 1; (c = *pattern); pattern++)
{
if ((c == ';' || c == '>') && match == FALSE)
{
*pattern = NUL; /* Terminate the string */
match = mch_check_filetype(fname, type_start);
*pattern = c; /* Restore the terminator */
type_start = pattern + 1;
}
if (c == '>')
break;
}
/* (c should never be NUL, but check anyway) */
if (match == FALSE || c == NUL)
regmatch.regprog = NULL; /* Doesn't match - don't check pat. */
else if (*pattern == NUL)
{
regmatch.regprog = NULL; /* Vim will try to free regprog later */
no_pattern = TRUE; /* Always matches - don't check pat. */
}
else
regmatch.regprog = vim_regcomp(pattern + 1, RE_MAGIC);
}
else
#endif
{
if (prog != NULL)
regmatch.regprog = prog;
else
regmatch.regprog = vim_regcomp(pattern, RE_MAGIC);
}
/*
* Try for a match with the pattern with:
* 1. the full file name, when the pattern has a '/'.
* 2. the short file name, when the pattern has a '/'.
* 3. the tail of the file name, when the pattern has no '/'.
*/
if (
#ifdef FEAT_OSFILETYPE
/* If the check is for a filetype only and we don't care
* about the path then skip all the regexp stuff.
*/
no_pattern ||
#endif
(regmatch.regprog != NULL
&& ((allow_dirs
&& (vim_regexec(®match, fname, (colnr_T)0)
|| (sfname != NULL
&& vim_regexec(®match, sfname, (colnr_T)0))))
|| (!allow_dirs && vim_regexec(®match, tail, (colnr_T)0)))))
result = TRUE;
if (prog == NULL)
vim_free(regmatch.regprog);
return result;
}
#endif
#if defined(FEAT_WILDIGN) || defined(PROTO)
/*
* 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(list, sfname, ffname)
char_u *list;
char_u *sfname;
char_u *ffname;
{
char_u buf[100];
char_u *tail;
char_u *regpat;
char allow_dirs;
int match;
char_u *p;
tail = gettail(sfname);
/* 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)
break;
match = match_file_pat(regpat, NULL, ffname, sfname,
tail, (int)allow_dirs);
vim_free(regpat);
if (match)
return TRUE;
}
return FALSE;
}
#endif
/*
* 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 "\ ".
*
* If FEAT_OSFILETYPE defined then pass initial <type> through unchanged. Eg:
* '<html>myfile' becomes '<html>^myfile$' -- leonard.
*
* Returns NULL when out of memory.
*/
/*ARGSUSED*/
char_u *
file_pat_to_reg_pat(pat, pat_end, allow_dirs, no_bslash)
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 */
{
int size;
char_u *endp;
char_u *reg_pat;
char_u *p;
int i;
int nested = 0;
int add_dollar = TRUE;
#ifdef FEAT_OSFILETYPE
int check_length = 0;
#endif
if (allow_dirs != NULL)
*allow_dirs = FALSE;
if (pat_end == NULL)
pat_end = pat + STRLEN(pat);
#ifdef FEAT_OSFILETYPE
/* Find out how much of the string is the filetype check */
if (*pat == '<')
{
/* Count chars until the next '>' */
for (p = pat + 1; p < pat_end && *p != '>'; p++)
;
if (p < pat_end)
{
/* Pattern is of the form <.*>.* */
check_length = p - pat + 1;
if (p + 1 >= pat_end)
{
/* The 'pattern' is a filetype check ONLY */
reg_pat = (char_u *)alloc(check_length + 1);
if (reg_pat != NULL)
{
mch_memmove(reg_pat, pat, (size_t)check_length);
reg_pat[check_length] = NUL;
}
return reg_pat;
}
}
/* else: there was no closing '>' - assume it was a normal pattern */
}
pat += check_length;
size = 2 + check_length;
#else
size = 2; /* '^' at start, '$' at end */
#endif
for (p = pat; p < pat_end; p++)
{
switch (*p)
{
case '*':
case '.':
case ',':
case '{':
case '}':
case '~':
size += 2; /* extra backslash */
break;
#ifdef BACKSLASH_IN_FILENAME
case '\\':
case '/':
size += 4; /* could become "[\/]" */
break;
#endif
default:
size++;
# ifdef FEAT_MBYTE
if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1)
{
++p;
++size;
}
# endif
break;
}
}
reg_pat = alloc(size + 1);
if (reg_pat == NULL)
return NULL;
#ifdef FEAT_OSFILETYPE
/* Copy the type check in to the start. */
if (check_length)
mch_memmove(reg_pat, pat - check_length, (size_t)check_length);
i = check_length;
#else
i = 0;
#endif
if (pat[0] == '*')
while (pat[0] == '*' && pat < pat_end - 1)
pat++;
else
reg_pat[i++] = '^';
endp = pat_end - 1;
if (*endp == '*')
{
while (endp - pat > 0 && *endp == '*')
endp--;
add_dollar = FALSE;
}
for (p = pat; *p && nested >= 0 && p <= endp; p++)
{
switch (*p)
{
case '*':
reg_pat[i++] = '.';
reg_pat[i++] = '*';
while (p[1] == '*') /* "**" matches like "*" */
++p;
break;
case '.':
#ifdef RISCOS
if (allow_dirs != NULL)
*allow_dirs = TRUE;
/* FALLTHROUGH */
#endif
case '~':
reg_pat[i++] = '\\';
reg_pat[i++] = *p;
break;
case '?':
#ifdef RISCOS
case '#':
#endif
reg_pat[i++] = '.';
break;
case '\\':
if (p[1] == NUL)
break;
#ifdef BACKSLASH_IN_FILENAME
if (!no_bslash)
{
/* translate:
* "\x" to "\\x" e.g., "dir\file"
* "\*" to "\\.*" e.g., "dir\*.c"
* "\?" to "\\." e.g., "dir\??.c"
* "\+" to "\+" e.g., "fileX\+.c"
*/
if ((vim_isfilec(p[1]) || p[1] == '*' || p[1] == '?')
&& p[1] != '+')
{
reg_pat[i++] = '[';
reg_pat[i++] = '\\';
reg_pat[i++] = '/';
reg_pat[i++] = ']';
if (allow_dirs != NULL)
*allow_dirs = TRUE;
break;
}
}
#endif
if (*++p == '?'
#ifdef BACKSLASH_IN_FILENAME
&& no_bslash
#endif
)
reg_pat[i++] = '?';
else
if (*p == ',')
reg_pat[i++] = ',';
else
{
if (allow_dirs != NULL && vim_ispathsep(*p)
#ifdef BACKSLASH_IN_FILENAME
&& (!no_bslash || *p != '\\')
#endif
)
*allow_dirs = TRUE;
reg_pat[i++] = '\\';
reg_pat[i++] = *p;
}
break;
#ifdef BACKSLASH_IN_FILENAME
case '/':
reg_pat[i++] = '[';
reg_pat[i++] = '\\';
reg_pat[i++] = '/';
reg_pat[i++] = ']';
if (allow_dirs != NULL)
*allow_dirs = TRUE;
break;
#endif
case '{':
reg_pat[i++] = '\\';
reg_pat[i++] = '(';
nested++;
break;
case '}':
reg_pat[i++] = '\\';
reg_pat[i++] = ')';
--nested;
break;
case ',':
if (nested)
{
reg_pat[i++] = '\\';
reg_pat[i++] = '|';
}
else
reg_pat[i++] = ',';
break;
default:
# ifdef FEAT_MBYTE
if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1)
reg_pat[i++] = *p++;
else
# endif
if (allow_dirs != NULL && vim_ispathsep(*p))
*allow_dirs = TRUE;
reg_pat[i++] = *p;
break;
}
}
if (add_dollar)
reg_pat[i++] = '$';
reg_pat[i] = NUL;
if (nested != 0)
{
if (nested < 0)
EMSG(_("E219: Missing {."));
else
EMSG(_("E220: Missing }."));
vim_free(reg_pat);
reg_pat = NULL;
}
return reg_pat;
}
|