diff options
-rw-r--r-- | runtime/doc/autocmd.txt | 2 | ||||
-rw-r--r-- | runtime/doc/builtin.txt | 4 | ||||
-rw-r--r-- | runtime/doc/editing.txt | 39 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 7 | ||||
-rw-r--r-- | runtime/doc/news.txt | 6 | ||||
-rw-r--r-- | runtime/doc/recover.txt | 9 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 5 | ||||
-rw-r--r-- | runtime/lua/vim/_editor.lua | 17 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 4 | ||||
-rw-r--r-- | src/nvim/eval.lua | 4 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 2 | ||||
-rw-r--r-- | src/nvim/eval/userfunc.c | 2 | ||||
-rw-r--r-- | src/nvim/memline.c | 258 | ||||
-rw-r--r-- | src/nvim/message.c | 28 | ||||
-rw-r--r-- | test/functional/ex_cmds/swapfile_preserve_recover_spec.lua | 58 |
15 files changed, 267 insertions, 178 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 90211fc5db..7ca9996e15 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -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 diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index c5c941a4bf..6de7166f83 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -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 diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 10b578fc73..7df2eb9742 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -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. - Also see |++opt| and |+cmd|. - *:edit!_f* -:e[dit]! [++opt] [+cmd] {file} - Edit {file} always. Discard any changes to the - current buffer. + 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_#* *: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 diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 3cd5ea13f6..c41237b862 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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 diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 3c20e52155..f0d01f92e7 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -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()| diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index b05bcd6bc8..e6b5b06744 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -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 ~ diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 3bf844a139..e9c04443a6 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -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* diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 7f09fc8038..1ba7d6163d 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -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() diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 55c324d739..5a3a5f3890 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -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 diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 30278bed1b..28fb9c6a5c 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -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 diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index ee70731c7c..4dd3f193e6 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -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 diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index e4adf9f340..ca98aad6bc 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -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 diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 5898e6aa7d..716d05e27a 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -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' diff --git a/src/nvim/message.c b/src/nvim/message.c index af19d0ab87..97402276b2 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -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); } diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 3e0f6d1fcc..436873b464 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -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() |