Merge #9956 from justinmk/vim-8.1.1231

vim-patch:8.1.1231, swap-related patches
This commit is contained in:
Justin M. Keyes 2019-04-30 01:26:33 +02:00 committed by GitHub
commit 63526f2eee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 335 additions and 62 deletions

View File

@ -2321,6 +2321,8 @@ submatch({nr} [, {list}]) String or List
specific match in ":s" or substitute()
substitute({expr}, {pat}, {sub}, {flags})
String all {pat} in {expr} replaced with {sub}
swapinfo({fname}) Dict information about swap file {fname}
swapname({expr}) String swap file of buffer {expr}
synID({lnum}, {col}, {trans}) Number syntax ID at {lnum} and {col}
synIDattr({synID}, {what} [, {mode}])
String attribute {what} of syntax ID {synID}
@ -7758,6 +7760,31 @@ substitute({expr}, {pat}, {sub}, {flags}) *substitute()*
|submatch()| returns. Example: >
:echo substitute(s, '\(\x\x\)', {m -> '0x' . m[1]}, 'g')
swapinfo({fname}) swapinfo()
The result is a dictionary, which holds information about the
swapfile {fname}. The available fields are:
version VIM version
user user name
host host name
fname original file name
pid PID of the VIM process that created the swap
file
mtime last modification time in seconds
inode Optional: INODE number of the file
dirty 1 if file was modified, 0 if not
In case of failure an "error" item is added with the reason:
Cannot open file: file not found or in accessible
Cannot read file: cannot read first block
Not a swap file: does not contain correct block ID
Magic number mismatch: Info in first block is invalid
swapname({expr}) *swapname()*
The result is the swap file path of the buffer {expr}.
For the use of {expr}, see |bufname()| above.
If buffer {expr} is the current buffer, the result is equal to
|:swapname| (unless no swap file).
If buffer {expr} has no swap file, returns an empty string.
synID({lnum}, {col}, {trans}) *synID()*
The result is a Number, which is the syntax ID at the position
{lnum} and {col} in the current window.

View File

@ -205,6 +205,13 @@ something wrong. It may be one of these two situations.
NEWER than swap file! ~
NOTE that in the following situation Vim knows the swap file is not useful and
will automatically delete it:
- The file is a valid swap file (Magic number is correct).
- The flag that the file was modified is not set.
- The process is not running.
UNREADABLE SWAP FILE
Sometimes the line

View File

@ -5221,7 +5221,7 @@ bool garbage_collect(bool testing)
(void)garbage_collect(testing);
}
} else if (p_verbose > 0) {
verb_msg((char_u *)_(
verb_msg(_(
"Not enough memory to set references, garbage collection aborted!"));
}
#undef ABORTING
@ -11474,25 +11474,21 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL};
/*
* "inputrestore()" function
*/
/// "inputrestore()" function
static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
if (!GA_EMPTY(&ga_userinput)) {
--ga_userinput.ga_len;
ga_userinput.ga_len--;
restore_typeahead((tasave_T *)(ga_userinput.ga_data)
+ ga_userinput.ga_len);
/* default return is zero == OK */
+ ga_userinput.ga_len);
// default return is zero == OK
} else if (p_verbose > 1) {
verb_msg((char_u *)_("called inputrestore() more often than inputsave()"));
rettv->vval.v_number = 1; /* Failed */
verb_msg(_("called inputrestore() more often than inputsave()"));
rettv->vval.v_number = 1; // Failed
}
}
/*
* "inputsave()" function
*/
/// "inputsave()" function
static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
// Add an entry to the stack of typeahead storage.
@ -11500,9 +11496,7 @@ static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
save_typeahead(p);
}
/*
* "inputsecret()" function
*/
/// "inputsecret()" function
static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
cmdline_star++;
@ -16423,6 +16417,27 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
/// "swapinfo(swap_filename)" function
static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
tv_dict_alloc_ret(rettv);
get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
}
/// "swapname(expr)" function
static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->v_type = VAR_STRING;
buf_T *buf = tv_get_buf(&argvars[0], false);
if (buf == NULL
|| buf->b_ml.ml_mfp == NULL
|| buf->b_ml.ml_mfp->mf_fname == NULL) {
rettv->vval.v_string = NULL;
} else {
rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
}
}
/// "synID(lnum, col, trans)" function
static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{

View File

@ -301,6 +301,8 @@ return {
strwidth={args=1},
submatch={args={1, 2}},
substitute={args=4},
swapinfo={args={1}},
swapname={args={1}},
synID={args=3},
synIDattr={args={2, 3}},
synIDtrans={args=1},

View File

@ -1656,12 +1656,7 @@ int tv_dict_add_special(dict_T *const d, const char *const key,
/// Add a string entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
/// @param[in] key Key to add.
/// @param[in] key_len Key length.
/// @param[in] val String to add.
///
/// @return OK in case of success, FAIL when key already exists.
/// @see tv_dict_add_allocated_str
int tv_dict_add_str(dict_T *const d,
const char *const key, const size_t key_len,
const char *const val)
@ -1670,6 +1665,27 @@ int tv_dict_add_str(dict_T *const d,
return tv_dict_add_allocated_str(d, key, key_len, xstrdup(val));
}
/// Add a string entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
/// @param[in] key Key to add.
/// @param[in] key_len Key length.
/// @param[in] val String to add. NULL adds empty string.
/// @param[in] len Use this many bytes from `val`, or -1 for whole string.
///
/// @return OK in case of success, FAIL when key already exists.
int tv_dict_add_str_len(dict_T *const d,
const char *const key, const size_t key_len,
char *const val, int len)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
char *s = val ? val : "";
if (val != NULL) {
s = (len < 0) ? xstrdup(val) : xstrndup(val, (size_t)len);
}
return tv_dict_add_allocated_str(d, key, key_len, s);
}
/// Add a string entry to dictionary
///
/// Unlike tv_dict_add_str() saves val to the new dictionary item in place of

View File

@ -633,7 +633,7 @@ readfile (
#endif
}
/* If "Quit" selected at ATTENTION dialog, don't load the file */
// If "Quit" selected at ATTENTION dialog, don't load the file.
if (swap_exists_action == SEA_QUIT) {
if (!read_buffer && !read_stdin)
close(fd);

View File

@ -1520,10 +1520,11 @@ static void create_windows(mparm_T *parmp)
dorewind = FALSE;
curbuf = curwin->w_buffer;
if (curbuf->b_ml.ml_mfp == NULL) {
/* Set 'foldlevel' to 'foldlevelstart' if it's not negative. */
if (p_fdls >= 0)
// Set 'foldlevel' to 'foldlevelstart' if it's not negative..
if (p_fdls >= 0) {
curwin->w_p_fdl = p_fdls;
/* When getting the ATTENTION prompt here, use a dialog */
}
// When getting the ATTENTION prompt here, use a dialog.
swap_exists_action = SEA_DIALOG;
set_buflisted(TRUE);

View File

@ -71,6 +71,7 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
#include "nvim/os/process.h"
#include "nvim/os/input.h"
#ifndef UNIX /* it's in os/unix_defs.h for Unix */
@ -1453,14 +1454,47 @@ static char *make_percent_swname(const char *dir, char *name)
return d;
}
#ifdef UNIX
static bool process_still_running;
#endif
/*
* Give information about an existing swap file.
* Returns timestamp (0 when unknown).
*/
/// Return information found in swapfile "fname" in dictionary "d".
/// This is used by the swapinfo() function.
void get_b0_dict(const char *fname, dict_T *d)
{
int fd;
struct block0 b0;
if ((fd = os_open(fname, O_RDONLY, 0)) >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
if (ml_check_b0_id(&b0) == FAIL) {
tv_dict_add_str(d, S_LEN("error"), "Not a swap file");
} else if (b0_magic_wrong(&b0)) {
tv_dict_add_str(d, S_LEN("error"), "Magic number mismatch");
} else {
// We have swap information.
tv_dict_add_str_len(d, S_LEN("version"), (char *)b0.b0_version, 10);
tv_dict_add_str_len(d, S_LEN("user"), (char *)b0.b0_uname,
B0_UNAME_SIZE);
tv_dict_add_str_len(d, S_LEN("host"), (char *)b0.b0_hname,
B0_HNAME_SIZE);
tv_dict_add_str_len(d, S_LEN("fname"), (char *)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("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));
}
} else {
tv_dict_add_str(d, S_LEN("error"), "Cannot read file");
}
close(fd);
} else {
tv_dict_add_str(d, S_LEN("error"), "Cannot open file");
}
}
/// Give information about an existing swap file.
/// Returns timestamp (0 when unknown).
static time_t swapfile_info(char_u *fname)
{
assert(fname != NULL);
@ -1530,12 +1564,10 @@ static time_t swapfile_info(char_u *fname)
if (char_to_long(b0.b0_pid) != 0L) {
MSG_PUTS(_("\n process ID: "));
msg_outnum(char_to_long(b0.b0_pid));
#if defined(UNIX)
if (kill((pid_t)char_to_long(b0.b0_pid), 0) == 0) {
if (os_proc_running((int)char_to_long(b0.b0_pid))) {
MSG_PUTS(_(" (STILL RUNNING)"));
process_still_running = true;
}
#endif
}
if (b0_magic_wrong(&b0)) {
@ -1552,6 +1584,51 @@ static time_t swapfile_info(char_u *fname)
return x;
}
/// Returns TRUE if the swap file looks OK and there are no changes, thus it
/// can be safely deleted.
static time_t swapfile_unchanged(char *fname)
{
struct block0 b0;
int ret = true;
// Swap file must exist.
if (!os_path_exists((char_u *)fname)) {
return false;
}
// must be able to read the first block
int fd = os_open(fname, O_RDONLY, 0);
if (fd < 0) {
return false;
}
if (read_eintr(fd, &b0, sizeof(b0)) != sizeof(b0)) {
close(fd);
return false;
}
// the ID and magic number must be correct
if (ml_check_b0_id(&b0) == FAIL|| b0_magic_wrong(&b0)) {
ret = false;
}
// must be unchanged
if (b0.b0_dirty) {
ret = false;
}
// process must be known and not running.
long pid = char_to_long(b0.b0_pid);
if (pid == 0L || os_proc_running((int)pid)) {
ret = false;
}
// TODO(bram): Should we check if the swap file was created on the current
// system? And the current user?
close(fd);
return ret;
}
static int recov_file_names(char_u **names, char_u *path, int prepend_dot)
FUNC_ATTR_NONNULL_ALL
{
@ -3353,17 +3430,24 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
&& vim_strchr(p_shm, SHM_ATTENTION) == NULL) {
int choice = 0;
#ifdef UNIX
process_still_running = false;
#endif
/*
* If there is a SwapExists autocommand and we can handle
* the response, trigger it. It may return 0 to ask the
* user anyway.
*/
if (swap_exists_action != SEA_NONE
&& has_autocmd(EVENT_SWAPEXISTS, (char_u *) buf_fname, buf))
choice = do_swapexists(buf, (char_u *) fname);
// It's safe to delete the swap file if all these are true:
// - the edited file exists
// - the swap file has no changes and looks OK
if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) {
choice = 4;
if (p_verbose > 0) {
verb_msg(_("Found a swap file that is not useful, deleting it"));
}
}
// If there is a SwapExists autocommand and we can handle the
// response, trigger it. It may return 0 to ask the user anyway.
if (choice == 0
&& swap_exists_action != SEA_NONE
&& has_autocmd(EVENT_SWAPEXISTS, (char_u *)buf_fname, buf)) {
choice = do_swapexists(buf, (char_u *)fname);
}
if (choice == 0) {
// Show info about the existing swap file.
@ -3395,21 +3479,18 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
xstrlcat(name, sw_msg_2, name_len);
choice = do_dialog(VIM_WARNING, (char_u *)_("VIM - ATTENTION"),
(char_u *)name,
# if defined(UNIX)
process_still_running
? (char_u *)_(
"&Open Read-Only\n&Edit anyway\n&Recover"
"\n&Quit\n&Abort") :
# endif
(char_u *)_(
"&Open Read-Only\n&Edit anyway\n&Recover"
"\n&Delete it\n&Quit\n&Abort"),
1, NULL, false);
# if defined(UNIX)
if (process_still_running && choice >= 4)
choice++; /* Skip missing "Delete it" button */
# endif
if (process_still_running && choice >= 4) {
choice++; // Skip missing "Delete it" button.
}
xfree(name);
// pretend screen didn't scroll, need redraw anyway

View File

@ -133,15 +133,11 @@ int msg(char_u *s)
return msg_attr_keep(s, 0, false, false);
}
/*
* Like msg() but keep it silent when 'verbosefile' is set.
*/
int verb_msg(char_u *s)
/// Like msg() but keep it silent when 'verbosefile' is set.
int verb_msg(char *s)
{
int n;
verbose_enter();
n = msg_attr_keep(s, 0, false, false);
int n = msg_attr_keep((char_u *)s, 0, false, false);
verbose_leave();
return n;

View File

@ -265,3 +265,9 @@ Dictionary os_proc_info(int pid)
return pinfo;
}
#endif
/// Return true if process `pid` is running.
bool os_proc_running(int pid)
{
return uv_kill(pid, 0) == 0;
}

View File

@ -1,5 +1,9 @@
" Tests for the swap feature
func s:swapname()
return trim(execute('swapname'))
endfunc
" Tests for 'directory' option.
func Test_swap_directory()
if !has("unix")
@ -17,7 +21,7 @@ func Test_swap_directory()
" Verify that the swap file doesn't exist in the current directory
call assert_equal([], glob(".Xtest1*.swp", 1, 1, 1))
edit Xtest1
let swfname = split(execute("swapname"))[0]
let swfname = s:swapname()
call assert_equal([swfname], glob(swfname, 1, 1, 1))
" './dir', swap file in a directory relative to the file
@ -27,7 +31,7 @@ func Test_swap_directory()
edit Xtest1
call assert_equal([], glob(swfname, 1, 1, 1))
let swfname = "Xtest2/Xtest1.swp"
call assert_equal(swfname, split(execute("swapname"))[0])
call assert_equal(swfname, s:swapname())
call assert_equal([swfname], glob("Xtest2/*", 1, 1, 1))
" 'dir', swap file in directory relative to the current dir
@ -38,7 +42,7 @@ func Test_swap_directory()
edit Xtest2/Xtest3
call assert_equal(["Xtest2/Xtest3"], glob("Xtest2/*", 1, 1, 1))
let swfname = "Xtest.je/Xtest3.swp"
call assert_equal(swfname, split(execute("swapname"))[0])
call assert_equal(swfname, s:swapname())
call assert_equal([swfname], glob("Xtest.je/*", 1, 1, 1))
set dir&
@ -61,3 +65,120 @@ func Test_missing_dir()
set directory&
call delete('Xswapdir', 'rf')
endfunc
func Test_swapinfo()
new Xswapinfo
call setline(1, ['one', 'two', 'three'])
w
let fname = s:swapname()
call assert_match('Xswapinfo', fname)
let info = swapinfo(fname)
let ver = printf('VIM %d.%d', v:version / 100, v:version % 100)
call assert_equal(ver, info.version)
call assert_match('\w', info.user)
" host name is truncated to 39 bytes in the swap file
call assert_equal(hostname()[:38], info.host)
call assert_match('Xswapinfo', info.fname)
call assert_match(0, info.dirty)
call assert_equal(getpid(), info.pid)
call assert_match('^\d*$', info.mtime)
if has_key(info, 'inode')
call assert_match('\d', info.inode)
endif
bwipe!
call delete(fname)
call delete('Xswapinfo')
let info = swapinfo('doesnotexist')
call assert_equal('Cannot open file', info.error)
call writefile(['burp'], 'Xnotaswapfile')
let info = swapinfo('Xnotaswapfile')
call assert_equal('Cannot read file', info.error)
call delete('Xnotaswapfile')
call writefile([repeat('x', 10000)], 'Xnotaswapfile')
let info = swapinfo('Xnotaswapfile')
call assert_equal('Not a swap file', info.error)
call delete('Xnotaswapfile')
endfunc
func Test_swapname()
edit Xtest1
let expected = s:swapname()
call assert_equal(expected, swapname('%'))
new Xtest2
let buf = bufnr('%')
let expected = s:swapname()
wincmd p
call assert_equal(expected, swapname(buf))
new Xtest3
setlocal noswapfile
call assert_equal('', swapname('%'))
bwipe!
call delete('Xtest1')
call delete('Xtest2')
call delete('Xtest3')
endfunc
func Test_swapfile_delete()
throw 'skipped: need the "blob" feature for this test'
autocmd! SwapExists
function s:swap_exists()
let v:swapchoice = s:swap_choice
let s:swapname = v:swapname
let s:filename = expand('<afile>')
endfunc
augroup test_swapfile_delete
autocmd!
autocmd SwapExists * call s:swap_exists()
augroup END
" Create a valid swapfile by editing a file.
split XswapfileText
call setline(1, ['one', 'two', 'three'])
write " file is written, not modified
" read the swapfile as a Blob
let swapfile_name = swapname('%')
let swapfile_bytes = readfile(swapfile_name, 'B')
" Close the file and recreate the swap file.
" Now editing the file will run into the process still existing
quit
call writefile(swapfile_bytes, swapfile_name)
let s:swap_choice = 'e'
let s:swapname = ''
split XswapfileText
quit
call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
" Write the swapfile with a modified PID, now it will be automatically
" deleted. Process one should never be Vim.
let swapfile_bytes[24:27] = 0z01000000
call writefile(swapfile_bytes, swapfile_name)
let s:swapname = ''
split XswapfileText
quit
call assert_equal('', s:swapname)
" Now set the modified flag, the swap file will not be deleted
let swapfile_bytes[28 + 80 + 899] = 0x55
call writefile(swapfile_bytes, swapfile_name)
let s:swapname = ''
split XswapfileText
quit
call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
call delete('XswapfileText')
call delete(swapfile_name)
augroup test_swapfile_delete
autocmd!
augroup END
augroup! test_swapfile_delete
endfunc

View File

@ -1131,8 +1131,9 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
/* If there is no undo information at all, quit here after deleting any
* existing undo file. */
if (buf->b_u_numhead == 0 && buf->b_u_line_ptr == NULL) {
if (p_verbose > 0)
verb_msg((char_u *)_("Skipping undo file write, nothing to undo"));
if (p_verbose > 0) {
verb_msg(_("Skipping undo file write, nothing to undo"));
}
goto theend;
}