feat: ignore swapfile for running Nvim processes #25336

Problem:
The swapfile "E325: ATTENTION" dialog is displayed when editing a file
already open in another (running) Nvim. Usually this behavior is
annoying and irrelevant:
- "Recover" and the other options ("Open readonly", "Quit", "Abort") are
  almost never wanted.
- swapfiles are less relevant for "multi-Nvim" since 'autoread' is
  enabled by default.
  - Even less relevant if user enables 'autowrite'.

Solution:
Define a default SwapExists handler which does the following:
1. If the swapfile is owned by a running Nvim process, automatically
   chooses "(E)dit anyway" (caveat: this creates a new, extra swapfile,
   which is mostly harmless and ignored except by `:recover` or `nvim -r`.
2. Shows a 1-line "ignoring swapfile..." message.
3. Users can disable the default SwapExists handler via `autocmd! nvim_swapfile`.
This commit is contained in:
Justin M. Keyes 2023-10-04 06:31:25 -07:00 committed by GitHub
parent 1e7e9ee91f
commit 29fe883aa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 267 additions and 178 deletions

View File

@ -118,7 +118,7 @@ manually. Mostly the screen will not scroll up, thus there is no hit-enter
prompt. When one command outputs two messages this can happen anyway.
==============================================================================
3. Removing autocommands *autocmd-remove*
3. Removing autocommands *autocmd!* *autocmd-remove*
:au[tocmd]! [group] {event} {aupat} [++once] [++nested] {cmd}
Remove all autocommands associated with {event} and

View File

@ -7843,8 +7843,8 @@ swapinfo({fname}) *swapinfo()*
user user name
host host name
fname original file name
pid PID of the Vim process that created the swap
file
pid PID of the Nvim process that created the swap
file, or zero if not running.
mtime last modification time in seconds
inode Optional: INODE number of the file
dirty 1 if file was modified, 0 if not

View File

@ -169,33 +169,26 @@ If you want to keep the changed buffer without saving it, switch on the
2. Editing a file *edit-a-file*
*:e* *:edit* *reload*
:e[dit] [++opt] [+cmd] Edit the current file. This is useful to re-edit the
:e[dit][!] [++opt] [+cmd]
Edit the current file. This is useful to re-edit the
current file, when it has been changed outside of Vim.
This fails when changes have been made to the current
buffer and 'autowriteall' isn't set or the file can't
be written.
Also see |++opt| and |+cmd|.
*:edit!* *discard*
:e[dit]! [++opt] [+cmd]
Edit the current file always. Discard any changes to
the current buffer. This is useful if you want to
start all over again.
If [!] is given, unsaved changes in the current buffer
are discarded. Without [!] the command fails if there
are unsaved changes, unless 'autowriteall' is set and
the file can be written.
Also see |++opt| and |+cmd|.
*:edit_f*
:e[dit] [++opt] [+cmd] {file}
:e[dit][!] [++opt] [+cmd] {file}
Edit {file}.
This fails when changes have been made to the current
buffer, unless 'hidden' is set or 'autowriteall' is
set and the file can be written.
*:edit!_f*
If [!] is given, unsaved changes in the current buffer
are discarded. Without [!] the command fails if there
are unsaved changes, unless 'hidden' is set or
'autowriteall' is set and the file can be written.
Also see |++opt| and |+cmd|.
*:edit!_f*
:e[dit]! [++opt] [+cmd] {file}
Edit {file} always. Discard any changes to the
current buffer.
Also see |++opt| and |+cmd|.
*:edit_#* *:e#*
:e[dit] [++opt] [+cmd] #[count]
Edit the [count]th buffer (as shown by |:files|).
@ -1224,10 +1217,10 @@ MULTIPLE WINDOWS AND BUFFERS *window-exit*
*:confirm* *:conf*
:conf[irm] {command} Execute {command}, and use a dialog when an
operation has to be confirmed. Can be used on the
|:q|, |:qa| and |:w| commands (the latter to override
a read-only setting), and any other command that can
fail in such a way, such as |:only|, |:buffer|,
|:bdelete|, etc.
|:edit|, |:q|, |:qa| and |:w| commands (the latter to
override a read-only setting), and any commands that
can fail because of unsaved changes, such as |:only|,
|:buffer|, |:bdelete|, etc.
Examples: >
:confirm w foo

View File

@ -2276,12 +2276,13 @@ v:stderr |channel-id| corresponding to stderr. The value is always 2;
:call chansend(v:stderr, "error: toaster empty\n")
<
*v:swapname* *swapname-variable*
v:swapname Only valid when executing |SwapExists| autocommands: Name of
the swap file found. Read-only.
v:swapname Name of the swapfile found.
Only valid during |SwapExists| event.
Read-only.
*v:swapchoice* *swapchoice-variable*
v:swapchoice |SwapExists| autocommands can set this to the selected choice
for handling an existing swap file:
for handling an existing swapfile:
'o' Open read-only
'e' Edit anyway
'r' Recover

View File

@ -114,6 +114,12 @@ The following new APIs and features were added.
• Builtin TUI can now recognize "super" (|<D-|) and "meta" (|<T-|) modifiers in a
terminal emulator that supports |tui-csiu|.
• Editor
• By default, the swapfile "ATTENTION" |E325| dialog is skipped if the
swapfile is owned by a running Nvim process, instead of prompting. If you
always want the swapfile dialog, delete the default SwapExists handler:
`autocmd! nvim_swapfile`. |default-autocmds|
• LSP
• LSP method names are available in |vim.lsp.protocol.Methods|.
• Implemented LSP inlay hints: |vim.lsp.inlay_hint()|

View File

@ -83,6 +83,15 @@ Detecting an existing swap file ~
You can find this in the user manual, section |11.3|.
*W325*
The default |SwapExists| handler (|default-autocmds|) skips the |E325| prompt
(selects "(E)dit") if the swapfile owner process (1) is still running and (2)
was started by the current user. This presumes that you normally don't want
to be bothered with the |ATTENTION| message just because you happen to edit
the same file from multiple Nvim instances. In the worst case (a system
crash) there will be more than one swapfile for the file; use |:recover| to
inspect all of its swapfiles.
Updating the swapfile ~

View File

@ -139,6 +139,11 @@ nvim_terminal:
nvim_cmdwin:
- CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|.
nvim_swapfile:
- SwapExists: Skips the swapfile prompt (sets |v:swapchoice| to "e") when the
swapfile is owned by a running Nvim process. Shows |W325| "Ignoring
swapfile…" message.
==============================================================================
New Features *nvim-features*

View File

@ -1147,11 +1147,28 @@ function vim._init_default_autocmds()
end
end,
})
vim.api.nvim_create_autocmd({ 'CmdwinEnter' }, {
pattern = '[:>]',
group = vim.api.nvim_create_augroup('nvim_cmdwin', {}),
command = 'syntax sync minlines=1 maxlines=1',
})
vim.api.nvim_create_autocmd({ 'SwapExists' }, {
pattern = '*',
group = vim.api.nvim_create_augroup('nvim_swapfile', {}),
callback = function()
local info = vim.fn.swapinfo(vim.v.swapname)
local user = vim.uv.os_get_passwd().username
local iswin = 1 == vim.fn.has('win32')
if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then
vim.v.swapchoice = '' -- Show the prompt.
return
end
vim.v.swapchoice = 'e' -- Choose "(E)dit".
vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid))
end,
})
end
function vim._init_defaults()

View File

@ -9312,8 +9312,8 @@ function vim.fn.swapfilelist() end
--- user user name
--- host host name
--- fname original file name
--- pid PID of the Vim process that created the swap
--- file
--- pid PID of the Nvim process that created the swap
--- file, or zero if not running.
--- mtime last modification time in seconds
--- inode Optional: INODE number of the file
--- dirty 1 if file was modified, 0 if not

View File

@ -11123,8 +11123,8 @@ M.funcs = {
user user name
host host name
fname original file name
pid PID of the Vim process that created the swap
file
pid PID of the Nvim process that created the swap
file, or zero if not running.
mtime last modification time in seconds
inode Optional: INODE number of the file
dirty 1 if file was modified, 0 if not

View File

@ -8236,7 +8236,7 @@ static void f_swapfilelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
tv_dict_alloc_ret(rettv);
get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
swapfile_dict(tv_get_string(argvars), rettv->vval.v_dict);
}
/// "swapname(expr)" function

View File

@ -2491,7 +2491,7 @@ void ex_function(exarg_T *eap)
} else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
nextcmd = line_arg;
} else if (*p != NUL && *p != '"' && p_verbose > 0) {
give_warning2(_("W22: Text found after :endfunction: %s"), p, true);
swmsg(true, _("W22: Text found after :endfunction: %s"), p);
}
if (nextcmd != NULL) {
// Another command follows. If the line came from "eap" we

View File

@ -168,17 +168,15 @@ enum {
B0_MAGIC_CHAR = 0x55,
};
// Block zero holds all info about the swap file. This is the first block in
// the file.
// Block zero holds all info about the swapfile. This is the first block in the file.
//
// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing
// swap files unusable!
// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing swapfiles unusable!
//
// If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in vim.h!!
//
// This block is built up of single bytes, to make it portable across
// different machines. b0_magic_* is used to check the byte order and size of
// variables, because the rest of the swap file is not portable.
// variables, because the rest of the swapfile is not portable.
typedef struct {
char b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1.
char b0_version[10]; // Vim version string
@ -210,8 +208,7 @@ typedef struct {
// EOL_MAC + 1.
#define B0_FF_MASK 3
// Swap file is in directory of edited file. Used to find the file from
// different mount points.
// Swapfile is in directory of edited file. Used to find the file from different mount points.
#define B0_SAME_DIR 4
// The 'fileencoding' is at the end of b0_fname[], with a NUL in front of it.
@ -289,14 +286,14 @@ int ml_open(buf_T *buf)
buf->b_p_swf = false;
}
// When 'updatecount' is non-zero swap file may be opened later.
// When 'updatecount' is non-zero swapfile may be opened later.
if (!buf->terminal && p_uc && buf->b_p_swf) {
buf->b_may_swap = true;
} else {
buf->b_may_swap = false;
}
// Open the memfile. No swap file is created yet.
// Open the memfile. No swapfile is created yet.
memfile_T *mfp = mf_open(NULL, 0);
if (mfp == NULL) {
goto error;
@ -335,7 +332,7 @@ int ml_open(buf_T *buf)
}
// Always sync block number 0 to disk, so we can check the file name in
// the swap file in findswapname(). Don't do this for a help files or
// the swapfile in findswapname(). Don't do this for a help files or
// a spell buffer though.
// Only works when there's a swapfile, otherwise it's done when the file
// is created.
@ -386,17 +383,17 @@ error:
}
/// ml_setname() is called when the file name of "buf" has been changed.
/// It may rename the swap file.
/// It may rename the swapfile.
void ml_setname(buf_T *buf)
{
bool success = false;
memfile_T *mfp = buf->b_ml.ml_mfp;
if (mfp->mf_fd < 0) { // there is no swap file yet
// When 'updatecount' is 0 and 'noswapfile' there is no swap file.
// For help files we will make a swap file now.
if (mfp->mf_fd < 0) { // there is no swapfile yet
// When 'updatecount' is 0 and 'noswapfile' there is no swapfile.
// For help files we will make a swapfile now.
if (p_uc != 0 && (cmdmod.cmod_flags & CMOD_NOSWAPFILE) == 0) {
ml_open_file(buf); // create a swap file
ml_open_file(buf); // create a swapfile
}
return;
}
@ -423,13 +420,13 @@ void ml_setname(buf_T *buf)
success = true;
break;
}
// need to close the swap file before renaming
// need to close the swapfile before renaming
if (mfp->mf_fd >= 0) {
close(mfp->mf_fd);
mfp->mf_fd = -1;
}
// try to rename the swap file
// try to rename the swapfile
if (vim_rename(mfp->mf_fname, fname) == 0) {
success = true;
mf_free_fnames(mfp);
@ -440,10 +437,10 @@ void ml_setname(buf_T *buf)
xfree(fname); // this fname didn't work, try another
}
if (mfp->mf_fd == -1) { // need to (re)open the swap file
if (mfp->mf_fd == -1) { // need to (re)open the swapfile
mfp->mf_fd = os_open(mfp->mf_fname, O_RDWR, 0);
if (mfp->mf_fd < 0) {
// could not (re)open the swap file, what can we do????
// could not (re)open the swapfile, what can we do????
emsg(_("E301: Oops, lost the swap file!!!"));
return;
}
@ -466,7 +463,7 @@ void ml_open_files(void)
}
}
/// Open a swap file for an existing memfile, if there is no swap file yet.
/// Open a swapfile for an existing memfile, if there is no swapfile yet.
/// If we are unable to find a file name, mf_fname will be NULL
/// and the memfile will be in memory only (no recovery possible).
void ml_open_file(buf_T *buf)
@ -495,7 +492,7 @@ void ml_open_file(buf_T *buf)
if (*dirp == NUL) {
break;
}
// There is a small chance that between choosing the swap file name
// There is a small chance that between choosing the swapfile name
// and creating it, another Vim creates the file. In that case the
// creation will fail and we will use another directory.
char *fname = findswapname(buf, &dirp, NULL, &found_existing_dir);
@ -514,7 +511,7 @@ void ml_open_file(buf_T *buf)
if (mf_sync(mfp, MFS_ZERO) == OK) {
// Mark all blocks that should be in the swapfile as dirty.
// Needed for when the 'swapfile' option was reset, so that
// the swap file was deleted, and then on again.
// the swapfile was deleted, and then on again.
mf_set_dirty(mfp);
break;
}
@ -531,12 +528,12 @@ void ml_open_file(buf_T *buf)
no_wait_return--;
}
// don't try to open a swap file again
// don't try to open a swapfile again
buf->b_may_swap = false;
}
/// If still need to create a swap file, and starting to edit a not-readonly
/// file, or reading into an existing buffer, create a swap file now.
/// If still need to create a swapfile, and starting to edit a not-readonly
/// file, or reading into an existing buffer, create a swapfile now.
///
/// @param newfile reading file into new buffer
void check_need_swap(bool newfile)
@ -553,7 +550,7 @@ void check_need_swap(bool newfile)
/// Close memline for buffer 'buf'.
///
/// @param del_file if true, delete the swap file
/// @param del_file if true, delete the swapfile
void ml_close(buf_T *buf, int del_file)
{
if (buf->b_ml.ml_mfp == NULL) { // not open
@ -643,7 +640,7 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what)
mf_put(mfp, hp, true, false);
}
/// Write file name and timestamp into block 0 of a swap file.
/// Write file name and timestamp into block 0 of a swapfile.
/// Also set buf->b_mtime.
/// Don't use NameBuff[]!!!
static void set_b0_fname(ZeroBlock *b0p, buf_T *buf)
@ -695,7 +692,7 @@ static void set_b0_fname(ZeroBlock *b0p, buf_T *buf)
add_b0_fenc(b0p, curbuf);
}
/// Update the B0_SAME_DIR flag of the swap file. It's set if the file and the
/// Update the B0_SAME_DIR flag of the swapfile. It's set if the file and the
/// swapfile for "buf" are in the same directory.
/// This is fail safe: if we are not sure the directories are equal the flag is
/// not set.
@ -724,27 +721,30 @@ static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf)
}
}
/// Return true if the process with number "b0p->b0_pid" is still running.
/// "swap_fname" is the name of the swap file, if it's from before a reboot then
/// the result is false;
static bool swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
/// Returns the PID of the process that owns the swapfile, if it is running.
///
/// @param b0p swapfile data
/// @param swap_fname Name of the swapfile. If it's from before a reboot, the result is 0.
///
/// @return PID, or 0 if process is not running or the swapfile is from before a reboot.
static int swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
{
FileInfo st;
double uptime;
// If the system rebooted after when the swap file was written then the
// If the system rebooted after when the swapfile was written then the
// process can't be running now.
if (os_fileinfo(swap_fname, &st)
&& uv_uptime(&uptime) == 0
&& (Timestamp)st.stat.st_mtim.tv_sec < os_time() - (Timestamp)uptime) {
return false;
return 0;
}
return os_proc_running((int)char_to_long(b0p->b0_pid));
int pid = (int)char_to_long(b0p->b0_pid);
return os_proc_running(pid) ? pid : 0;
}
/// Try to recover curbuf from the .swp file.
///
/// @param checkext if true, check the extension and detect whether it is a
/// swap file.
/// @param checkext if true, check the extension and detect whether it is a swapfile.
void ml_recover(bool checkext)
{
buf_T *buf = NULL;
@ -766,8 +766,8 @@ void ml_recover(bool checkext)
int called_from_main = (curbuf->b_ml.ml_mfp == NULL);
int attr = HL_ATTR(HLF_E);
// If the file name ends in ".s[a-w][a-z]" we assume this is the swap file.
// Otherwise a search is done to find the swap file(s).
// If the file name ends in ".s[a-w][a-z]" we assume this is the swapfile.
// Otherwise a search is done to find the swapfile(s).
char *fname = curbuf->b_fname;
if (fname == NULL) { // When there is no file name
fname = "";
@ -782,17 +782,17 @@ void ml_recover(bool checkext)
} else {
directly = false;
// count the number of matching swap files
// count the number of matching swapfiles
len = recover_names(fname, false, NULL, 0, NULL);
if (len == 0) { // no swap files found
if (len == 0) { // no swapfiles found
semsg(_("E305: No swap file found for %s"), fname);
goto theend;
}
int i;
if (len == 1) { // one swap file found, use it
if (len == 1) { // one swapfile found, use it
i = 1;
} else { // several swap files found, choose
// list the names of the swap files
} else { // several swapfiles found, choose
// list the names of the swapfiles
(void)recover_names(fname, true, NULL, 0, NULL);
msg_putchar('\n');
msg_puts(_("Enter number of swap file to use (0 to quit): "));
@ -801,7 +801,7 @@ void ml_recover(bool checkext)
goto theend;
}
}
// get the swap file name that will be used
// get the swapfile name that will be used
(void)recover_names(fname, false, NULL, i, &fname_used);
}
if (fname_used == NULL) {
@ -812,7 +812,7 @@ void ml_recover(bool checkext)
getout(1);
}
// Allocate a buffer structure for the swap file that is used for recovery.
// Allocate a buffer structure for the swapfile that is used for recovery.
// Only the memline in it is really used.
buf = xmalloc(sizeof(buf_T));
@ -825,7 +825,7 @@ void ml_recover(bool checkext)
buf->b_ml.ml_locked = NULL; // no locked block
buf->b_ml.ml_flags = 0;
// open the memfile from the old swap file
// open the memfile from the old swapfile
p = xstrdup(fname_used); // save "fname_used" for the message:
// mf_open() will consume "fname_used"!
mfp = mf_open(fname_used, O_RDONLY);
@ -837,7 +837,7 @@ void ml_recover(bool checkext)
buf->b_ml.ml_mfp = mfp;
// The page size set in mf_open() might be different from the page size
// used in the swap file, we must get it from block 0. But to read block
// used in the swapfile, we must get it from block 0. But to read block
// 0 we need a page size. Use the minimal size for block 0 here, it will
// be set to the real value below.
mfp->mf_page_size = MIN_SWAP_PAGE_SIZE;
@ -910,7 +910,7 @@ void ml_recover(bool checkext)
b0p = hp->bh_data;
}
// If .swp file name given directly, use name from swap file for buffer.
// If .swp file name given directly, use name from swapfile for buffer.
if (directly) {
expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
@ -929,7 +929,7 @@ void ml_recover(bool checkext)
smsg(0, _("Original file \"%s\""), NameBuff);
msg_putchar('\n');
// check date of swap file and original file
// check date of swapfile and original file
FileInfo org_file_info;
FileInfo swp_file_info;
long mtime = char_to_long(b0p->b0_mtime);
@ -968,7 +968,7 @@ void ml_recover(bool checkext)
0, MAXLNUM, NULL, READ_NEW, false);
}
// Use the 'fileformat' and 'fileencoding' as stored in the swap file.
// Use the 'fileformat' and 'fileencoding' as stored in the swapfile.
if (b0_ff != 0) {
set_fileformat(b0_ff - 1, OPT_LOCAL);
}
@ -1231,7 +1231,7 @@ theend:
}
mf_close(mfp, false); // will also xfree(mfp->mf_fname)
}
if (buf != NULL) { // may be NULL if swap file not found.
if (buf != NULL) { // may be NULL if swapfile not found.
xfree(buf->b_ml.ml_stack);
xfree(buf);
}
@ -1243,20 +1243,20 @@ theend:
}
}
/// Find the names of swap files in current directory and the directory given
/// Find the names of swapfiles in current directory and the directory given
/// with the 'directory' option.
///
/// Used to:
/// - list the swap files for "vim -r"
/// - count the number of swap files when recovering
/// - list the swap files when recovering
/// - list the swap files for swapfilelist()
/// - find the name of the n'th swap file when recovering
/// - list the swapfiles for "vim -r"
/// - count the number of swapfiles when recovering
/// - list the swapfiles when recovering
/// - list the swapfiles for swapfilelist()
/// - find the name of the n'th swapfile when recovering
///
/// @param fname base for swap file name
/// @param do_list when true, list the swap file names
/// @param fname base for swapfile name
/// @param do_list when true, list the swapfile names
/// @param ret_list when not NULL add file names to it
/// @param nr when non-zero, return nr'th swap file name
/// @param nr when non-zero, return nr'th swapfile name
/// @param fname_out result when "nr" > 0
int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fname_out)
{
@ -1273,7 +1273,7 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn
if (fname != NULL) {
#ifdef HAVE_READLINK
// Expand symlink in the file name, because the swap file is created
// Expand symlink in the file name, because the swapfile is created
// with the actual file instead of with the symlink.
if (resolve_symlink(fname, fname_buf) == OK) {
fname_res = fname_buf;
@ -1342,9 +1342,9 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn
num_files = 0;
}
// When no swap file found, wildcard expansion might have failed (e.g.
// When no swapfile found, wildcard expansion might have failed (e.g.
// not able to execute the shell).
// Try finding a swap file by simply adding ".swp" to the file name.
// Try finding a swapfile by simply adding ".swp" to the file name.
if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) {
char *swapname = modname(fname_res, ".swp", true);
if (swapname != NULL) {
@ -1402,7 +1402,7 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn
if (num_files) {
for (int i = 0; i < num_files; i++) {
// print the swap file name
// print the swapfile name
msg_outnum(++file_count);
msg_puts(". ");
msg_puts(path_tail(files[i]));
@ -1456,12 +1456,13 @@ char *make_percent_swname(const char *dir, const char *name)
return d;
}
static bool process_still_running;
// PID of swapfile owner, or zero if not running.
static int process_running;
/// This is used by the swapinfo() function.
/// For Vimscript "swapinfo()".
///
/// @return information found in swapfile "fname" in dictionary "d".
void get_b0_dict(const char *fname, dict_T *d)
void swapfile_dict(const char *fname, dict_T *d)
{
int fd;
ZeroBlock b0;
@ -1482,7 +1483,7 @@ void get_b0_dict(const char *fname, dict_T *d)
tv_dict_add_str_len(d, S_LEN("fname"), b0.b0_fname,
B0_FNAME_SIZE_ORG);
tv_dict_add_nr(d, S_LEN("pid"), char_to_long(b0.b0_pid));
tv_dict_add_nr(d, S_LEN("pid"), swapfile_process_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
@ -1496,7 +1497,7 @@ void get_b0_dict(const char *fname, dict_T *d)
}
}
/// Give information about an existing swap file.
/// Loads info from swapfile `fname`, and displays it to the user.
///
/// @return timestamp (0 when unknown).
static time_t swapfile_info(char *fname)
@ -1509,7 +1510,7 @@ static time_t swapfile_info(char *fname)
char uname[B0_UNAME_SIZE];
#endif
// print the swap file date
// print the swapfile date
FileInfo file_info;
if (os_fileinfo(fname, &file_info)) {
#ifdef UNIX
@ -1567,9 +1568,8 @@ static time_t swapfile_info(char *fname)
if (char_to_long(b0.b0_pid) != 0L) {
msg_puts(_("\n process ID: "));
msg_outnum((int)char_to_long(b0.b0_pid));
if (swapfile_process_running(&b0, fname)) {
if ((process_running = swapfile_process_running(&b0, fname))) {
msg_puts(_(" (STILL RUNNING)"));
process_still_running = true;
}
}
@ -1589,13 +1589,12 @@ static time_t swapfile_info(char *fname)
return x;
}
/// @return true if the swap file looks OK and there are no changes, thus it
/// can be safely deleted.
/// @return true if the swapfile looks OK and there are no changes, thus it can be safely deleted.
static bool swapfile_unchanged(char *fname)
{
ZeroBlock b0;
// Swap file must exist.
// Swapfile must exist.
if (!os_path_exists(fname)) {
return false;
}
@ -1653,7 +1652,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot)
{
int num_names = 0;
// May also add the file name with a dot prepended, for swap file in same
// May also add the file name with a dot prepended, for swapfile in same
// dir as original file.
if (prepend_dot) {
names[num_names] = modname(path, ".sw?", true);
@ -1663,7 +1662,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot)
num_names++;
}
// Form the normal swap file name pattern by appending ".sw?".
// Form the normal swapfile name pattern by appending ".sw?".
names[num_names] = concat_fnames(path, ".sw?", false);
if (num_names >= 1) { // check if we have the same name twice
char *p = names[num_names - 1];
@ -1724,7 +1723,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
/// sync one buffer, including negative blocks
///
/// after this all the blocks are in the swap file
/// after this all the blocks are in the swapfile
///
/// Used for the :preserve command and when the original file has been
/// changed or deleted.
@ -3132,7 +3131,7 @@ int resolve_symlink(const char *fname, char *buf)
}
#endif
/// Make swap file name out of the file name and a directory name.
/// Make swapfile name out of the file name and a directory name.
///
/// @return pointer to allocated memory or NULL.
char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
@ -3141,7 +3140,7 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
#ifdef HAVE_READLINK
char fname_buf[MAXPATHL];
// Expand symlink in the file name, so that we put the swap file with the
// Expand symlink in the file name, so that we put the swapfile with the
// actual file instead of with the symlink.
if (resolve_symlink(fname, fname_buf) == OK) {
fname_res = fname_buf;
@ -3162,7 +3161,7 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
return r;
}
// Prepend a '.' to the swap file name for the current directory.
// Prepend a '.' to the swapfile name for the current directory.
char *r = modname(fname_res, ".swp",
dir_name[0] == '.' && dir_name[1] == NUL);
if (r == NULL) { // out of memory
@ -3174,14 +3173,11 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
return s;
}
/// Get file name to use for swap file or backup file.
/// Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir'
/// option "dname".
/// - If "dname" is ".", return "fname" (swap file in dir of file).
/// - If "dname" starts with "./", insert "dname" in "fname" (swap file
/// relative to dir of file).
/// - Otherwise, prepend "dname" to the tail of "fname" (swap file in specific
/// dir).
/// Get file name to use for swapfile or backup file.
/// Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir' option "dname".
/// - If "dname" is ".", return "fname" (swapfile in dir of file).
/// - If "dname" starts with "./", insert "dname" in "fname" (swapfile relative to dir of file).
/// - Otherwise, prepend "dname" to the tail of "fname" (swapfile in specific dir).
///
/// The return value is an allocated string and can be NULL.
///
@ -3212,10 +3208,10 @@ char *get_file_in_dir(char *fname, char *dname)
return retval;
}
/// Print the ATTENTION message: info about an existing swap file.
/// Print the ATTENTION message: info about an existing swapfile.
///
/// @param buf buffer being edited
/// @param fname swap file name
/// @param fname swapfile name
static void attention_message(buf_T *buf, char *fname)
{
assert(buf->b_fname != NULL);
@ -3299,7 +3295,7 @@ static int do_swapexists(buf_T *buf, char *fname)
return 0;
}
/// Find out what name to use for the swap file for buffer 'buf'.
/// Find out what name to use for the swapfile for buffer 'buf'.
///
/// Several names are tried to find one that does not exist. Last directory in
/// option is automatically created.
@ -3308,20 +3304,20 @@ static int do_swapexists(buf_T *buf, char *fname)
/// not being able to open the swap or undo file.
/// @note May trigger SwapExists autocmd, pointers may change!
///
/// @param[in] buf Buffer for which swap file names needs to be found.
/// @param[in] buf Buffer for which swapfile names needs to be found.
/// @param[in,out] dirp Pointer to a list of directories. When out of memory,
/// is set to NULL. Is advanced to the next directory in
/// the list otherwise.
/// @param[in] old_fname Allowed existing swap file name. Except for this
/// @param[in] old_fname Allowed existing swapfile name. Except for this
/// case, name of the non-existing file is used.
/// @param[in,out] found_existing_dir If points to true, then new directory
/// for swap file is not created. At first
/// for swapfile is not created. At first
/// findswapname() call this argument must
/// point to false. This parameter may only
/// be set to true by this function, it is
/// never set to false.
///
/// @return [allocated] Name of the swap file.
/// @return [allocated] Name of the swapfile.
static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_existing_dir)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
@ -3333,7 +3329,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
char *dir_name = xmalloc(dir_len);
(void)copy_option_part(dirp, dir_name, dir_len, ",");
// we try different names until we find one that does not exist yet
// We try different swapfile names until we find one that does not exist yet.
char *fname = makeswapname(buf_fname, buf->b_ffname, buf, dir_name);
while (true) {
@ -3346,7 +3342,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
break;
}
// check if the swapfile already exists
// Extra security check: When a swap file is a symbolic link, this
// Extra security check: When a swapfile is a symbolic link, this
// is most likely a symlink attack.
FileInfo file_info;
bool file_or_link_found = os_fileinfo_link(fname, &file_info);
@ -3365,17 +3361,17 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
// Give an error message, unless recovering, no file name, we are
// viewing a help file or when the path of the file is different
// (happens when all .swp files are in one directory).
if (!recoverymode && buf_fname != NULL
&& !buf->b_help && !(buf->b_flags & BF_DUMMY)) {
if (!recoverymode && buf_fname != NULL && !buf->b_help && !(buf->b_flags & BF_DUMMY)) {
int fd;
ZeroBlock b0;
int differ = false;
// Try to read block 0 from the swap file to get the original
// file name (and inode number).
// Try to read block 0 from the swapfile to get the original file name (and inode number).
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
process_running = swapfile_process_running(&b0, fname);
// If the swapfile has the same directory as the
// buffer don't compare the directory names, they can
// have a different mountpoint.
@ -3393,8 +3389,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
}
}
} else {
// The name in the swap file may be
// "~user/path/file". Expand it first.
// The name in the swapfile may be "~user/path/file". Expand it first.
expand_env(b0.b0_fname, NameBuff, MAXPATHL);
if (fnamecmp_ino(buf->b_ffname, NameBuff,
char_to_long(b0.b0_ino))) {
@ -3405,16 +3400,16 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
close(fd);
}
// give the ATTENTION message when there is an old swap file
// for the current file, and the buffer was not recovered.
// Show the ATTENTION message when:
// - there is an old swapfile for the current file
// - the buffer was not recovered
if (differ == false && !(curbuf->b_flags & BF_RECOVERED)
&& vim_strchr(p_shm, SHM_ATTENTION) == NULL) {
int choice = 0;
process_still_running = false;
// It's safe to delete the swap file if all these are true:
// It's safe to delete the swapfile if all these are true:
// - the edited file exists
// - the swap file has no changes and looks OK
// - the swapfile has no changes and looks OK
if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) {
choice = 4;
if (p_verbose > 0) {
@ -3430,8 +3425,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
choice = do_swapexists(buf, fname);
}
process_running = 0; // Set by attention_message..swapfile_info.
if (choice == 0) {
// Show info about the existing swap file.
// Show info about the existing swapfile.
attention_message(buf, fname);
// We don't want a 'q' typed at the more-prompt
@ -3459,15 +3455,15 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
xstrlcat(name, sw_msg_2, name_len);
choice = do_dialog(VIM_WARNING, _("VIM - ATTENTION"),
name,
process_still_running
process_running
? _("&Open Read-Only\n&Edit anyway\n&Recover"
"\n&Quit\n&Abort") :
_("&Open Read-Only\n&Edit anyway\n&Recover"
"\n&Delete it\n&Quit\n&Abort"),
1, NULL, false);
if (process_still_running && choice >= 4) {
choice++; // Skip missing "Delete it" button.
if (process_running && choice >= 4) {
choice++; // Skip missing "Delete it" button.
}
xfree(name);
@ -3477,27 +3473,27 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
if (choice > 0) {
switch (choice) {
case 1:
case 1: // "Open Read-Only"
buf->b_p_ro = true;
break;
case 2:
case 2: // "Edit anyway"
break;
case 3:
case 3: // "Recover"
swap_exists_action = SEA_RECOVER;
break;
case 4:
case 4: // "Delete it"
os_remove(fname);
break;
case 5:
case 5: // "Quit"
swap_exists_action = SEA_QUIT;
break;
case 6:
case 6: // "Abort"
swap_exists_action = SEA_QUIT;
got_int = true;
break;
}
// If the file was deleted this fname can be used.
// If the swapfile was deleted this `fname` can be used.
if (!os_path_exists(fname)) {
break;
}
@ -3512,10 +3508,10 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
}
}
// Change the ".swp" extension to find another file that can be used.
// Permute the ".swp" extension to find a unique swapfile name.
// First decrement the last char: ".swo", ".swn", etc.
// If that still isn't enough decrement the last but one char: ".svz"
// Can happen when editing many "No Name" buffers.
// Can happen when many Nvim instances are editing the same file (including "No Name" buffers).
if (fname[n - 1] == 'a') { // ".s?a"
if (fname[n - 2] == 'a') { // ".saa": tried enough, give up
emsg(_("E326: Too many swap files found"));
@ -3553,7 +3549,7 @@ static int b0_magic_wrong(ZeroBlock *b0p)
|| b0p->b0_magic_char != B0_MAGIC_CHAR;
}
/// Compare current file name with file name from swap file.
/// Compare current file name with file name from swapfile.
/// Try to use inode numbers when possible.
/// Return non-zero when files are different.
///
@ -3563,7 +3559,7 @@ static int b0_magic_wrong(ZeroBlock *b0p)
/// because the device number cannot be used over a network.
/// - When a file does not exist yet (editing a new file) there is no inode
/// number.
/// - The file name in a swap file may not be valid on the current host. The
/// - The file name in a swapfile may not be valid on the current host. The
/// "~user" form is used whenever possible to avoid this.
///
/// This is getting complicated, let's make a table:
@ -3577,7 +3573,7 @@ static int b0_magic_wrong(ZeroBlock *b0p)
/// == 0 X OK OK fname_c != fname_s
/// X == 0 OK OK fname_c != fname_s
///
/// current file doesn't exist, file for swap file exist, file name(s) not
/// current file doesn't exist, file for swapfile exist, file name(s) not
/// available -> probably different
/// == 0 != 0 FAIL X true
/// == 0 != 0 X FAIL true
@ -3600,11 +3596,11 @@ static int b0_magic_wrong(ZeroBlock *b0p)
/// without making the block 0 incompatible with 32 bit versions.
///
/// @param fname_c current file name
/// @param fname_s file name from swap file
/// @param fname_s file name from swapfile
static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
{
uint64_t ino_c = 0; // ino of current file
uint64_t ino_s; // ino of file from swap file
uint64_t ino_s; // ino of file from swapfile
char buf_c[MAXPATHL]; // full path of fname_c
char buf_s[MAXPATHL]; // full path of fname_s
int retval_c; // flag: buf_c valid
@ -3616,7 +3612,7 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
}
// First we try to get the inode from the file name, because the inode in
// the swap file may be outdated. If that fails (e.g. this path is not
// the swapfile may be outdated. If that fails (e.g. this path is not
// valid on this machine), use the inode from block 0.
if (os_fileinfo(fname_s, &file_info)) {
ino_s = os_fileinfo_inode(&file_info);
@ -3638,7 +3634,7 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
// Can't compare inodes or file names, guess that the files are different,
// unless both appear not to exist at all, then compare with the file name
// in the swap file.
// in the swapfile.
if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL) {
return strcmp(fname_c, fname_s) != 0;
}
@ -3675,7 +3671,7 @@ static long char_to_long(const char *s_in)
return retval;
}
/// Set the flags in the first block of the swap file:
/// Set the flags in the first block of the swapfile:
/// - file is modified or not: buf->b_changed
/// - 'fileformat'
/// - 'fileencoding'

View File

@ -475,7 +475,14 @@ void trunc_string(const char *s, char *buf, int room_in, int buflen)
}
}
// Note: Caller of smsg() must check the resulting string is shorter than IOSIZE!!!
/// Shows a printf-style message with attributes.
///
/// Note: Caller must check the resulting string is shorter than IOSIZE!!!
///
/// @see semsg
/// @see swmsg
///
/// @param s printf-style format message
int smsg(int attr, const char *s, ...)
FUNC_ATTR_PRINTF(2, 3)
{
@ -757,6 +764,8 @@ void emsg_invreg(int name)
}
/// Print an error message with unknown number of arguments
///
/// @return whether the message was displayed
bool semsg(const char *const fmt, ...)
FUNC_ATTR_PRINTF(1, 2)
{
@ -3337,9 +3346,22 @@ void give_warning(const char *message, bool hl)
no_wait_return--;
}
void give_warning2(const char *const message, const char *const a1, bool hl)
/// Shows a warning, with optional highlighting.
///
/// @param hl enable highlighting
/// @param fmt printf-style format message
///
/// @see smsg
/// @see semsg
void swmsg(bool hl, const char *const fmt, ...)
FUNC_ATTR_PRINTF(2, 3)
{
vim_snprintf(IObuff, IOSIZE, message, a1);
va_list args;
va_start(args, fmt);
vim_vsnprintf(IObuff, IOSIZE, fmt, args);
va_end(args);
give_warning(IObuff, hl);
}

View File

@ -171,6 +171,7 @@ describe('swapfile detection', function()
local screen2 = Screen.new(256, 40)
screen2:attach()
exec(init)
command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
-- With shortmess+=F
command('set shortmess+=F')
@ -219,11 +220,29 @@ describe('swapfile detection', function()
nvim2:close()
end)
it('default SwapExists handler selects "(E)dit" and skips prompt', function()
exec(init)
command('edit Xfile1')
command("put ='some text...'")
command('preserve') -- Make sure the swap file exists.
local nvimpid = funcs.getpid()
local nvim1 = spawn(new_argv(), true, nil, true)
set_session(nvim1)
local screen = Screen.new(75, 18)
screen:attach()
exec(init)
feed(':edit Xfile1\n')
screen:expect({ any = ('W325: Ignoring swapfile from Nvim process %d'):format(nvimpid) })
nvim1:close()
end)
-- oldtest: Test_swap_prompt_splitwin()
it('selecting "q" in the attention prompt', function()
exec(init)
command('edit Xfile1')
command('preserve') -- should help to make sure the swap file exists
command('preserve') -- Make sure the swap file exists.
local screen = Screen.new(75, 18)
screen:set_default_attr_ids({
@ -235,7 +254,9 @@ describe('swapfile detection', function()
set_session(nvim1)
screen:attach()
exec(init)
command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
feed(':split Xfile1\n')
-- The default SwapExists handler does _not_ skip this prompt.
screen:expect({
any = pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^')
})
@ -267,6 +288,7 @@ describe('swapfile detection', function()
set_session(nvim2)
screen:attach()
exec(init)
command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
command('set more')
command('au bufadd * let foo_w = wincol()')
feed(':e Xfile1<CR>')
@ -300,8 +322,9 @@ describe('swapfile detection', function()
nvim2:close()
end)
-- oldtest: Test_nocatch_process_still_running()
it('allows deleting swapfile created before boot vim-patch:8.2.2586', function()
--- @param swapexists boolean Enable the default SwapExists handler.
--- @param on_swapfile_running fun(screen: any) Called after swapfile ("STILL RUNNING") prompt.
local function test_swapfile_after_reboot(swapexists, on_swapfile_running)
local screen = Screen.new(75, 30)
screen:set_default_attr_ids({
[0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
@ -311,6 +334,9 @@ describe('swapfile detection', function()
screen:attach()
exec(init)
if not swapexists then
command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
end
command('set nohidden')
exec([=[
@ -347,12 +373,7 @@ describe('swapfile detection', function()
os.rename('Xswap', swname)
feed(':edit Xswaptest<CR>')
screen:expect({any = table.concat({
pesc('{2:E325: ATTENTION}'),
'file name: .*Xswaptest',
'process ID: %d* %(STILL RUNNING%)',
pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'),
}, '.*')})
on_swapfile_running(screen)
feed('e')
@ -374,7 +395,26 @@ describe('swapfile detection', function()
}, '.*')})
feed('e')
end
-- oldtest: Test_nocatch_process_still_running()
it('swapfile created before boot vim-patch:8.2.2586', function()
test_swapfile_after_reboot(false, function(screen)
screen:expect({any = table.concat({
pesc('{2:E325: ATTENTION}'),
'file name: .*Xswaptest',
'process ID: %d* %(STILL RUNNING%)',
pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'),
}, '.*')})
end)
end)
it('swapfile created before boot + default SwapExists handler', function()
test_swapfile_after_reboot(true, function(screen)
screen:expect({ any = 'W325: Ignoring swapfile from Nvim process' })
end)
end)
end)
describe('quitting swapfile dialog on startup stops TUI properly', function()