mirror of
https://github.com/neovim/neovim
synced 2025-07-16 09:11:51 +00:00
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:
@ -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
|
||||
|
4
runtime/doc/builtin.txt
generated
4
runtime/doc/builtin.txt
generated
@ -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
|
||||
|
@ -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
|
||||
|
@ -2276,8 +2276,9 @@ 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
|
||||
|
@ -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()|
|
||||
|
@ -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 ~
|
||||
|
||||
|
@ -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*
|
||||
|
||||
|
@ -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()
|
||||
|
4
runtime/lua/vim/_meta/vimfn.lua
generated
4
runtime/lua/vim/_meta/vimfn.lua
generated
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -168,11 +168,9 @@ 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!!
|
||||
//
|
||||
@ -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.
|
||||
@ -724,10 +721,13 @@ 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;
|
||||
@ -736,15 +736,15 @@ static bool swapfile_process_running(const ZeroBlock *b0p, const char *swap_fnam
|
||||
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;
|
||||
@ -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)
|
||||
@ -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,8 +1589,7 @@ 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;
|
||||
@ -3175,13 +3174,10 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
|
||||
}
|
||||
|
||||
/// 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".
|
||||
/// 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" (swap file
|
||||
/// relative to dir of file).
|
||||
/// - Otherwise, prepend "dname" to the tail of "fname" (swap file in specific
|
||||
/// dir).
|
||||
/// - 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.
|
||||
///
|
||||
@ -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) {
|
||||
@ -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,13 +3400,13 @@ 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 swapfile if all these are true:
|
||||
// - the edited file exists
|
||||
// - the swapfile has no changes and looks OK
|
||||
@ -3430,6 +3425,7 @@ 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 swapfile.
|
||||
attention_message(buf, fname);
|
||||
@ -3459,14 +3455,14 @@ 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) {
|
||||
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"));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,9 +395,28 @@ 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()
|
||||
local swapdir = luv.cwd()..'/Xtest_swapquit_dir'
|
||||
local testfile = 'Xtest_swapquit_file1'
|
||||
|
Reference in New Issue
Block a user