aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.asan-blacklist2
-rw-r--r--src/nvim/CMakeLists.txt6
-rw-r--r--src/nvim/api/private/dispatch.c3
-rw-r--r--src/nvim/api/private/helpers.c47
-rw-r--r--src/nvim/api/vim.c8
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/buffer.c26
-rw-r--r--src/nvim/digraph.c37
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/ex_cmds.c122
-rw-r--r--src/nvim/ex_cmds2.c4
-rw-r--r--src/nvim/ex_docmd.c79
-rw-r--r--src/nvim/ex_getln.c4
-rw-r--r--src/nvim/getchar.c75
-rw-r--r--src/nvim/main.c19
-rw-r--r--src/nvim/normal.c4
-rw-r--r--src/nvim/quickfix.c484
-rw-r--r--src/nvim/screen.c21
-rw-r--r--src/nvim/tag.c8
-rw-r--r--src/nvim/testdir/test_exit.vim57
-rw-r--r--src/nvim/testdir/test_help.vim36
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim28
-rw-r--r--src/nvim/testdir/test_hlsearch.vim14
-rw-r--r--src/nvim/testdir/test_mksession.vim90
-rw-r--r--src/nvim/testdir/test_quickfix.vim335
-rw-r--r--src/nvim/testdir/test_timers.vim20
-rw-r--r--src/nvim/testdir/test_writefile.vim82
-rw-r--r--src/nvim/window.c32
28 files changed, 1219 insertions, 427 deletions
diff --git a/src/.asan-blacklist b/src/.asan-blacklist
index 9d7f721a88..b0eb9a62a9 100644
--- a/src/.asan-blacklist
+++ b/src/.asan-blacklist
@@ -4,4 +4,4 @@ fun:tv_dict_watcher_node_data
# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but
# this sometimes does not happen during teardown.
-func:loop_schedule_deferred
+fun:loop_schedule_deferred
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index b32d54b28e..29a4e1e163 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -660,4 +660,10 @@ add_custom_target(
DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE}
)
+add_custom_target(generated-sources DEPENDS
+ ${NVIM_GENERATED_FOR_SOURCES}
+ ${NVIM_GENERATED_FOR_HEADERS}
+ ${NVIM_GENERATED_SOURCES}
+)
+
add_subdirectory(po)
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index dec2b6c185..c08225bbfc 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -40,7 +40,8 @@ MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name,
map_get(String, MsgpackRpcRequestHandler)(methods, m);
if (!rv.fn) {
- api_set_error(error, kErrorTypeException, "Invalid method: %s",
+ api_set_error(error, kErrorTypeException, "Invalid method: %.*s",
+ m.size > 0 ? m.size : sizeof("<empty>"),
m.size > 0 ? m.data : "<empty>");
}
return rv;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f3e883de02..ecc0ede4a4 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -162,7 +162,7 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Key '%s' not found", key.data);
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
return (Object)OBJECT_INIT;
}
@@ -191,13 +191,12 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del,
}
if (key.size == 0) {
- api_set_error(err, kErrorTypeValidation,
- "Empty variable names aren't allowed");
+ api_set_error(err, kErrorTypeValidation, "Key name is empty");
return rv;
}
if (key.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "Key length is too high");
+ api_set_error(err, kErrorTypeValidation, "Key name is too long");
return rv;
}
@@ -220,7 +219,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del,
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
- api_set_error(err, kErrorTypeValidation, "Key does not exist: %s",
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s",
key.data);
} else {
// Return the old value
@@ -284,9 +283,7 @@ Object get_option_from(void *from, int type, String name, Error *err)
type, from);
if (!flags) {
- api_set_error(err,
- kErrorTypeValidation,
- "Invalid option name \"%s\"",
+ api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
name.data);
return rv;
}
@@ -303,15 +300,14 @@ Object get_option_from(void *from, int type, String name, Error *err)
rv.data.string.data = stringval;
rv.data.string.size = strlen(stringval);
} else {
- api_set_error(err,
- kErrorTypeException,
- "Unable to get value for option \"%s\"",
+ api_set_error(err, kErrorTypeException,
+ "Failed to get value for option '%s'",
name.data);
}
} else {
api_set_error(err,
kErrorTypeException,
- "Unknown type for option \"%s\"",
+ "Unknown type for option '%s'",
name.data);
}
@@ -336,24 +332,20 @@ void set_option_to(uint64_t channel_id, void *to, int type,
int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
if (flags == 0) {
- api_set_error(err,
- kErrorTypeValidation,
- "Invalid option name \"%s\"",
+ api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
name.data);
return;
}
if (value.type == kObjectTypeNil) {
if (type == SREQ_GLOBAL) {
- api_set_error(err,
- kErrorTypeException,
- "Unable to unset option \"%s\"",
+ api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
name.data);
return;
} else if (!(flags & SOPT_GLOBAL)) {
api_set_error(err,
kErrorTypeException,
- "Cannot unset option \"%s\" "
+ "Cannot unset option '%s' "
"because it doesn't have a global value",
name.data);
return;
@@ -370,7 +362,7 @@ void set_option_to(uint64_t channel_id, void *to, int type,
if (value.type != kObjectTypeBoolean) {
api_set_error(err,
kErrorTypeValidation,
- "Option \"%s\" requires a boolean value",
+ "Option '%s' requires a Boolean value",
name.data);
return;
}
@@ -378,17 +370,15 @@ void set_option_to(uint64_t channel_id, void *to, int type,
numval = value.data.boolean;
} else if (flags & SOPT_NUM) {
if (value.type != kObjectTypeInteger) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option \"%s\" requires an integer value",
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires an integer value",
name.data);
return;
}
if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err,
- kErrorTypeValidation,
- "Value for option \"%s\" is outside range",
+ api_set_error(err, kErrorTypeValidation,
+ "Value for option '%s' is out of range",
name.data);
return;
}
@@ -396,9 +386,8 @@ void set_option_to(uint64_t channel_id, void *to, int type,
numval = (int)value.data.integer;
} else {
if (value.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option \"%s\" requires a string value",
+ api_set_error(err, kErrorTypeValidation,
+ "Option '%s' requires a string value",
name.data);
return;
}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index e78b8c776d..fed20a272a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -502,7 +502,7 @@ Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1)
{
if (text.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "String length is too high");
+ api_set_error(err, kErrorTypeValidation, "String is too long");
return 0;
}
@@ -559,7 +559,7 @@ void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1)
{
if (dir.size >= MAXPATHL) {
- api_set_error(err, kErrorTypeValidation, "Directory string is too long");
+ api_set_error(err, kErrorTypeValidation, "Directory name is too long");
return;
}
@@ -1136,14 +1136,14 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err)
if (calls.items[i].type != kObjectTypeArray) {
api_set_error(err,
kErrorTypeValidation,
- "All items in calls array must be arrays");
+ "Items in calls array must be arrays");
goto validation_error;
}
Array call = calls.items[i].data.array;
if (call.size != 2) {
api_set_error(err,
kErrorTypeValidation,
- "All items in calls array must be arrays of size 2");
+ "Items in calls array must be arrays of size 2");
goto validation_error;
}
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index d0a3f38c6b..d002aaae43 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -34,6 +34,7 @@ return {
'CursorMovedI', -- cursor was moved in Insert mode
'DirChanged', -- directory changed
'EncodingChanged', -- after changing the 'encoding' option
+ 'ExitPre', -- before exiting
'FileAppendCmd', -- append to a file using command
'FileAppendPost', -- after appending to a file
'FileAppendPre', -- before appending to a file
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 64569c294b..384c7f77b2 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -252,7 +252,7 @@ open_buffer (
msg_silent = old_msg_silent;
// Help buffer is filtered.
- if (curbuf->b_help) {
+ if (bt_help(curbuf)) {
fix_help_buffer();
}
} else if (read_stdin) {
@@ -841,8 +841,8 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
* aborting() returns FALSE when closing a window. */
enter_cleanup(&cs);
- /* Quitting means closing the split window, nothing else. */
- win_close(curwin, TRUE);
+ // Quitting means closing the split window, nothing else.
+ win_close(curwin, true);
swap_exists_action = SEA_NONE;
swap_exists_did_quit = TRUE;
@@ -4651,10 +4651,10 @@ void ex_buffer_all(exarg_T *eap)
&& !ONE_WINDOW
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)
) {
- win_close(wp, FALSE);
- wpnext = firstwin; /* just in case an autocommand does
- something strange with windows */
- tpnext = first_tabpage; /* start all over...*/
+ win_close(wp, false);
+ wpnext = firstwin; // just in case an autocommand does
+ // something strange with windows
+ tpnext = first_tabpage; // start all over...
open_wins = 0;
} else
++open_wins;
@@ -4723,9 +4723,9 @@ void ex_buffer_all(exarg_T *eap)
* aborting() returns FALSE when closing a window. */
enter_cleanup(&cs);
- /* User selected Quit at ATTENTION prompt; close this window. */
- win_close(curwin, TRUE);
- --open_wins;
+ // User selected Quit at ATTENTION prompt; close this window.
+ win_close(curwin, true);
+ open_wins--;
swap_exists_action = SEA_NONE;
swap_exists_did_quit = TRUE;
@@ -4930,6 +4930,12 @@ chk_modeline (
return retval;
}
+// Return true if "buf" is a help buffer.
+bool bt_help(const buf_T *const buf)
+{
+ return buf != NULL && buf->b_help;
+}
+
/*
* Return special buffer name.
* Returns NULL when the buffer has a normal file name.
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index c0915224e5..8f7487262e 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -1359,6 +1359,12 @@ static digr_T digraphdefault[] =
{ 'f', 't', 0xfb05 },
{ 's', 't', 0xfb06 },
+ // extra alternatives, easier to remember
+ { 'W', '`', 0x1e80 },
+ { 'w', '`', 0x1e81 },
+ { 'Y', '`', 0x1ef2 },
+ { 'y', '`', 0x1ef3 },
+
// Vim 5.x compatible digraphs that don't conflict with the above
{ '~', '!', 161 }, // ¡
{ 'c', '|', 162 }, // ¢
@@ -1520,34 +1526,6 @@ static int getexactdigraph(int char1, int char2, int meta_char)
}
}
- if ((retval != 0) && !enc_utf8) {
- char_u buf[6], *to;
- vimconv_T vc;
-
- // Convert the Unicode digraph to 'encoding'.
- int i = utf_char2bytes(retval, buf);
- retval = 0;
- vc.vc_type = CONV_NONE;
-
- if (convert_setup(&vc, (char_u *)"utf-8", p_enc) == OK) {
- vc.vc_fail = true;
- assert(i >= 0);
- size_t len = (size_t)i;
- to = string_convert(&vc, buf, &len);
-
- if (to != NULL) {
- retval = utf_ptr2char(to);
- xfree(to);
- }
- (void)convert_setup(&vc, NULL, NULL);
- }
- }
-
- // Ignore multi-byte characters when not in multi-byte mode.
- if (!has_mbyte && (retval > 0xff)) {
- retval = 0;
- }
-
if (retval == 0) {
// digraph deleted or not found
if ((char1 == ' ') && meta_char) {
@@ -1654,8 +1632,7 @@ void listdigraphs(void)
tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE);
if ((tmp.result != 0)
- && (tmp.result != tmp.char2)
- && (has_mbyte || (tmp.result <= 255))) {
+ && (tmp.result != tmp.char2)) {
printdigraph(&tmp);
}
dp++;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index d750a47588..309c0fc062 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -9943,7 +9943,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
if (what_arg->v_type == VAR_UNKNOWN) {
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (is_qf || wp != NULL) {
- (void)get_errorlist(wp, -1, rettv->vval.v_list);
+ (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list);
}
} else {
tv_dict_alloc_ret(rettv);
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index ca975ee02a..ac5b3af459 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -4514,7 +4514,7 @@ void ex_help(exarg_T *eap)
* Re-use an existing help window or open a new one.
* Always open a new one for ":tab help".
*/
- if (!curwin->w_buffer->b_help
+ if (!bt_help(curwin->w_buffer)
|| cmdmod.tab != 0
) {
if (cmdmod.tab != 0) {
@@ -4522,7 +4522,7 @@ void ex_help(exarg_T *eap)
} else {
wp = NULL;
FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (wp2->w_buffer != NULL && wp2->w_buffer->b_help) {
+ if (bt_help(wp2->w_buffer)) {
wp = wp2;
break;
}
@@ -4675,49 +4675,66 @@ static int help_compare(const void *s1, const void *s2)
return strcmp(p1, p2);
}
-/*
- * Find all help tags matching "arg", sort them and return in matches[], with
- * the number of matches in num_matches.
- * The matches will be sorted with a "best" match algorithm.
- * When "keep_lang" is TRUE try keeping the language of the current buffer.
- */
-int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_lang)
+// Find all help tags matching "arg", sort them and return in matches[], with
+// the number of matches in num_matches.
+// The matches will be sorted with a "best" match algorithm.
+// When "keep_lang" is true try keeping the language of the current buffer.
+int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches,
+ bool keep_lang)
{
- char_u *s, *d;
int i;
- static char *(mtable[]) = {"*", "g*", "[*", "]*",
- "/*", "/\\*", "\"*", "**",
- "/\\(\\)", "/\\%(\\)",
- "?", ":?", "?<CR>", "g?", "g?g?", "g??",
- "/\\?", "/\\z(\\)", "\\=", ":s\\=",
- "[count]", "[quotex]",
- "[range]", ":[range]",
- "[pattern]", "\\|", "\\%$",
- "s/\\~", "s/\\U", "s/\\L",
- "s/\\1", "s/\\2", "s/\\3", "s/\\9"};
- static char *(rtable[]) = {"star", "gstar", "[star", "]star",
- "/star", "/\\\\star", "quotestar", "starstar",
- "/\\\\(\\\\)", "/\\\\%(\\\\)",
- "?", ":?", "?<CR>", "g?", "g?g?", "g??",
- "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=",
- "\\[count]", "\\[quotex]",
- "\\[range]", ":\\[range]",
- "\\[pattern]", "\\\\bar", "/\\\\%\\$",
- "s/\\\\\\~", "s/\\\\U", "s/\\\\L",
- "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"};
- int flags;
-
- d = IObuff; /* assume IObuff is long enough! */
-
- /*
- * Recognize a few exceptions to the rule. Some strings that contain '*'
- * with "star". Otherwise '*' is recognized as a wildcard.
- */
- for (i = (int)ARRAY_SIZE(mtable); --i >= 0; )
- if (STRCMP(arg, mtable[i]) == 0) {
- STRCPY(d, rtable[i]);
- break;
+ static const char *(mtable[]) = {
+ "*", "g*", "[*", "]*",
+ "/*", "/\\*", "\"*", "**",
+ "/\\(\\)", "/\\%(\\)",
+ "?", ":?", "?<CR>", "g?", "g?g?", "g??",
+ "-?", "q?", "v_g?",
+ "/\\?", "/\\z(\\)", "\\=", ":s\\=",
+ "[count]", "[quotex]",
+ "[range]", ":[range]",
+ "[pattern]", "\\|", "\\%$",
+ "s/\\~", "s/\\U", "s/\\L",
+ "s/\\1", "s/\\2", "s/\\3", "s/\\9"
+ };
+ static const char *(rtable[]) = {
+ "star", "gstar", "[star", "]star",
+ "/star", "/\\\\star", "quotestar", "starstar",
+ "/\\\\(\\\\)", "/\\\\%(\\\\)",
+ "?", ":?", "?<CR>", "g?", "g?g?", "g??",
+ "-?", "q?", "v_g?",
+ "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=",
+ "\\[count]", "\\[quotex]",
+ "\\[range]", ":\\[range]",
+ "\\[pattern]", "\\\\bar", "/\\\\%\\$",
+ "s/\\\\\\~", "s/\\\\U", "s/\\\\L",
+ "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"
+ };
+ static const char *(expr_table[]) = {
+ "!=?", "!~?", "<=?", "<?", "==?", "=~?",
+ ">=?", ">?", "is?", "isnot?"
+ };
+ char_u *d = IObuff; // assume IObuff is long enough!
+
+ if (STRNICMP(arg, "expr-", 5) == 0) {
+ // When the string starting with "expr-" and containing '?' and matches
+ // the table, it is taken literally. Otherwise '?' is recognized as a
+ // wildcard.
+ for (i = (int)ARRAY_SIZE(expr_table); --i >= 0; ) {
+ if (STRCMP(arg + 5, expr_table[i]) == 0) {
+ STRCPY(d, arg);
+ break;
+ }
+ }
+ } else {
+ // Recognize a few exceptions to the rule. Some strings that contain
+ // '*' with "star". Otherwise '*' is recognized as a wildcard.
+ for (i = (int)ARRAY_SIZE(mtable); --i >= 0; ) {
+ if (STRCMP(arg, mtable[i]) == 0) {
+ STRCPY(d, rtable[i]);
+ break;
+ }
}
+ }
if (i < 0) { /* no match in table */
/* Replace "\S" with "/\\S", etc. Otherwise every tag is matched.
@@ -4749,7 +4766,7 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la
if (*arg == '(' && arg[1] == '\'') {
arg++;
}
- for (s = arg; *s; s++) {
+ for (const char_u *s = arg; *s; s++) {
// Replace "|" with "bar" and '"' with "quote" to match the name of
// the tags for these commands.
// Replace "*" with ".*" and "?" with "." to match command line
@@ -4858,9 +4875,10 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la
*matches = (char_u **)"";
*num_matches = 0;
- flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE;
- if (keep_lang)
+ int flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE;
+ if (keep_lang) {
flags |= TAG_KEEP_LANG;
+ }
if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK
&& *num_matches > 0) {
/* Sort the matches found on the heuristic number that is after the
@@ -5017,11 +5035,9 @@ void fix_help_buffer(void)
const char_u *const f1 = fnames[i1];
const char_u *const f2 = fnames[i2];
const char_u *const t1 = path_tail(f1);
- if (fnamencmp(f1, f2, t1 - f1) != 0) {
- continue;
- }
+ const char_u *const t2 = path_tail(f2);
const char_u *const e1 = STRRCHR(t1, '.');
- const char_u *const e2 = STRRCHR(path_tail(f2), '.');
+ const char_u *const e2 = STRRCHR(t2, '.');
if (e1 == NULL || e2 == NULL) {
continue;
}
@@ -5032,8 +5048,10 @@ void fix_help_buffer(void)
fnames[i1] = NULL;
continue;
}
- if (fnamencmp(f1, f2, e1 - f1) != 0)
+ if (e1 - f1 != e2 - f2
+ || fnamencmp(f1, f2, e1 - f1) != 0) {
continue;
+ }
if (fnamecmp(e1, ".txt") == 0
&& fnamecmp(e2, fname + 4) == 0) {
/* use .abx instead of .txt */
@@ -5491,8 +5509,8 @@ static int next_sign_typenr = 1;
void ex_helpclose(exarg_T *eap)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
- if (win->w_buffer->b_help) {
- win_close(win, FALSE);
+ if (bt_help(win->w_buffer)) {
+ win_close(win, false);
return;
}
}
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index f5822535ba..6e695a8897 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1209,7 +1209,7 @@ int autowrite(buf_T *buf, int forceit)
return r;
}
-/// flush all buffers, except the ones that are readonly
+/// Flush all buffers, except the ones that are readonly or are never written.
void autowrite_all(void)
{
if (!(p_aw || p_awa) || !p_write) {
@@ -1217,7 +1217,7 @@ void autowrite_all(void)
}
FOR_ALL_BUFFERS(buf) {
- if (bufIsChanged(buf) && !buf->b_p_ro) {
+ if (bufIsChanged(buf) && !buf->b_p_ro && !bt_dontwrite(buf)) {
bufref_T bufref;
set_bufref(&bufref, buf);
(void)buf_write_all(buf, false);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 5a92a85c97..03f1446265 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5964,9 +5964,35 @@ void not_exiting(void)
exiting = FALSE;
}
-/*
- * ":quit": quit current window, quit Vim if the last window is closed.
- */
+static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
+{
+ apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
+
+ // Bail out when autocommands closed the window.
+ // Refuse to quit when the buffer in the last window is being closed (can
+ // only happen in autocommands).
+ if (!win_valid(wp)
+ || curbuf_locked()
+ || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
+ return true;
+ }
+
+ if (quit_all
+ || (check_more(false, forceit) == OK && only_one_window())) {
+ apply_autocmds(EVENT_EXITPRE, NULL, NULL, false, curbuf);
+ // Refuse to quit when locked or when the buffer in the last window is
+ // being closed (can only happen in autocommands).
+ if (curbuf_locked()
+ || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// ":quit": quit current window, quit Vim if the last window is closed.
+// ":{nr}quit": quit window {nr}
static void ex_quit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -5996,11 +6022,9 @@ static void ex_quit(exarg_T *eap)
if (curbuf_locked()) {
return;
}
- apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (!win_valid(wp)
- || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
+
+ // Trigger QuitPre and maybe ExitPre
+ if (before_quit_autocmds(wp, false, eap->forceit)) {
return;
}
@@ -6025,6 +6049,7 @@ static void ex_quit(exarg_T *eap)
if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) {
getout(0);
}
+ not_exiting();
// close window; may free buffer
win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
}
@@ -6057,10 +6082,8 @@ static void ex_quit_all(exarg_T *eap)
text_locked_msg();
return;
}
- apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, curbuf);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+
+ if (before_quit_autocmds(curwin, true, eap->forceit)) {
return;
}
@@ -6346,9 +6369,7 @@ static void ex_stop(exarg_T *eap)
}
}
-/*
- * ":exit", ":xit" and ":wq": Write file and exit Vim.
- */
+// ":exit", ":xit" and ":wq": Write file and quite the current window.
static void ex_exit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -6360,10 +6381,8 @@ static void ex_exit(exarg_T *eap)
text_locked_msg();
return;
}
- apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, curbuf);
- // Refuse to quit when locked or when the buffer in the last window is
- // being closed (can only happen in autocommands).
- if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0)) {
+
+ if (before_quit_autocmds(curwin, false, eap->forceit)) {
return;
}
@@ -6382,6 +6401,7 @@ static void ex_exit(exarg_T *eap)
// quit last window, exit Vim
getout(0);
}
+ not_exiting();
// Quit current window, may free the buffer.
win_close(curwin, !buf_hide(curwin->w_buffer));
}
@@ -9239,6 +9259,18 @@ static int ses_do_win(win_T *wp)
return true;
}
+static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
+{
+ int r;
+
+ if (wp->w_curswant == MAXCOL) {
+ r = fprintf(fd, "%snormal! $", spaces);
+ } else {
+ r = fprintf(fd, "%snormal! 0%d|", spaces, wp->w_virtcol + 1);
+ }
+ return r < 0 || put_eol(fd) == FAIL ? FAIL : OK;
+}
+
/*
* Write commands to "fd" to restore the view of a window.
* Caller must make sure 'scrolloff' is zero.
@@ -9405,14 +9437,11 @@ put_view(
(int64_t)(wp->w_virtcol + 1)) < 0
|| put_eol(fd) == FAIL
|| put_line(fd, "else") == FAIL
- || fprintf(fd, " normal! 0%d|", wp->w_virtcol + 1) < 0
- || put_eol(fd) == FAIL
+ || put_view_curpos(fd, wp, " ") == FAIL
|| put_line(fd, "endif") == FAIL)
return FAIL;
- } else {
- if (fprintf(fd, "normal! 0%d|", wp->w_virtcol + 1) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
+ } else if (put_view_curpos(fd, wp, "") == FAIL) {
+ return FAIL;
}
}
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 786e2cd12c..31231fe725 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -4677,7 +4677,7 @@ ExpandFromContext (
/* With an empty argument we would get all the help tags, which is
* very slow. Get matches for "help" instead. */
if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat,
- num_file, file, FALSE) == OK) {
+ num_file, file, false) == OK) {
cleanup_help_tags(*num_file, *file);
return OK;
}
@@ -6192,7 +6192,7 @@ static int open_cmdwin(void)
wp = curwin;
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
- win_close(wp, TRUE);
+ win_close(wp, true);
// win_close() may have already wiped the buffer when 'bh' is
// set to 'wipe'.
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index df185f1a5b..f13bede076 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -434,9 +434,8 @@ void flush_buffers(int flush_typeahead)
* of an escape sequence.
* In an xterm we get one char at a time and we have to get them all.
*/
- while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10L,
- typebuf.tb_change_cnt) != 0)
- ;
+ while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10L) != 0) {
+ }
typebuf.tb_off = MAXMAPLEN;
typebuf.tb_len = 0;
// Reset the flag that text received from a client or from feedkeys()
@@ -1697,22 +1696,20 @@ static int vgetorpeek(int advance)
os_breakcheck(); /* check for CTRL-C */
keylen = 0;
if (got_int) {
- /* flush all input */
- c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L,
- typebuf.tb_change_cnt);
- /*
- * If inchar() returns TRUE (script file was active) or we
- * are inside a mapping, get out of insert mode.
- * Otherwise we behave like having gotten a CTRL-C.
- * As a result typing CTRL-C in insert mode will
- * really insert a CTRL-C.
- */
+ // flush all input
+ c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L);
+ // If inchar() returns TRUE (script file was active) or we
+ // are inside a mapping, get out of insert mode.
+ // Otherwise we behave like having gotten a CTRL-C.
+ // As a result typing CTRL-C in insert mode will
+ // really insert a CTRL-C.
if ((c || typebuf.tb_maplen)
- && (State & (INSERT + CMDLINE)))
+ && (State & (INSERT + CMDLINE))) {
c = ESC;
- else
+ } else {
c = Ctrl_C;
- flush_buffers(TRUE); /* flush all typeahead */
+ }
+ flush_buffers(true); // flush all typeahead
if (advance) {
/* Also record this character, it might be needed to
@@ -2075,18 +2072,17 @@ static int vgetorpeek(int advance)
c = 0;
new_wcol = curwin->w_wcol;
new_wrow = curwin->w_wrow;
- if ( advance
- && typebuf.tb_len == 1
- && typebuf.tb_buf[typebuf.tb_off] == ESC
- && !no_mapping
- && ex_normal_busy == 0
- && typebuf.tb_maplen == 0
- && (State & INSERT)
- && (p_timeout
- || (keylen == KEYLEN_PART_KEY && p_ttimeout))
- && (c = inchar(typebuf.tb_buf + typebuf.tb_off
- + typebuf.tb_len, 3, 25L,
- typebuf.tb_change_cnt)) == 0) {
+ if (advance
+ && typebuf.tb_len == 1
+ && typebuf.tb_buf[typebuf.tb_off] == ESC
+ && !no_mapping
+ && ex_normal_busy == 0
+ && typebuf.tb_maplen == 0
+ && (State & INSERT)
+ && (p_timeout
+ || (keylen == KEYLEN_PART_KEY && p_ttimeout))
+ && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len,
+ 3, 25L)) == 0) {
colnr_T col = 0, vcol;
char_u *ptr;
@@ -2258,6 +2254,11 @@ static int vgetorpeek(int advance)
/*
* get a character: 3. from the user - get it
*/
+ if (typebuf.tb_len == 0) {
+ // timedout may have been set while waiting for a mapping
+ // that has a <Nop> RHS.
+ timedout = false;
+ }
wait_tb_len = typebuf.tb_len;
c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len,
typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1,
@@ -2269,7 +2270,7 @@ static int vgetorpeek(int advance)
? -1L
: ((keylen == KEYLEN_PART_KEY && p_ttm >= 0)
? p_ttm
- : p_tm)), typebuf.tb_change_cnt);
+ : p_tm)));
if (i != 0)
pop_showcmd();
@@ -2350,16 +2351,15 @@ static int vgetorpeek(int advance)
* Return the number of obtained characters.
* Return -1 when end of input script reached.
*/
-int
-inchar (
+int inchar(
char_u *buf,
int maxlen,
- long wait_time, /* milli seconds */
- int tb_change_cnt
+ long wait_time // milli seconds
)
{
int len = 0; // Init for GCC.
int retesc = false; // Return ESC with gotint.
+ const int tb_change_cnt = typebuf.tb_change_cnt;
if (wait_time == -1L || wait_time > 100L) {
// flush output before waiting
@@ -2430,10 +2430,19 @@ inchar (
len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt);
}
+ // If the typebuf was changed further down, it is like nothing was added by
+ // this call.
if (typebuf_changed(tb_change_cnt)) {
return 0;
}
+ // Note the change in the typeahead buffer, this matters for when
+ // vgetorpeek() is called recursively, e.g. using getchar(1) in a timer
+ // function.
+ if (len > 0 && ++typebuf.tb_change_cnt == 0) {
+ typebuf.tb_change_cnt = 1;
+ }
+
return fix_input_buffer(buf, len);
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index af7c194edc..d5e37929b9 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -892,6 +892,7 @@ static void command_line_scan(mparm_T *parmp)
set_option_value("rl", 1L, NULL, 0);
break;
}
+ case '?': // "-?" give help message (for MS-Windows)
case 'h': { // "-h" give help message
usage();
mch_exit(0);
@@ -1539,7 +1540,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
{
int arg_idx; /* index in argument list */
int i;
- int advance = TRUE;
+ bool advance = true;
win_T *win;
/*
@@ -1550,8 +1551,8 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
/* When w_arg_idx is -1 remove the window (see create_windows()). */
if (curwin->w_arg_idx == -1) {
- win_close(curwin, TRUE);
- advance = FALSE;
+ win_close(curwin, true);
+ advance = false;
}
arg_idx = 1;
@@ -1561,9 +1562,9 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
}
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
- ++arg_idx;
- win_close(curwin, TRUE);
- advance = FALSE;
+ arg_idx++;
+ win_close(curwin, true);
+ advance = false;
continue;
}
@@ -1578,7 +1579,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
win_enter(curwin->w_next, false);
}
}
- advance = TRUE;
+ advance = true;
// Only open the file if there is no file in this window yet (that can
// happen when vimrc contains ":sall").
@@ -1597,8 +1598,8 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
did_emsg = FALSE; /* avoid hit-enter prompt */
getout(1);
}
- win_close(curwin, TRUE);
- advance = FALSE;
+ win_close(curwin, true);
+ advance = false;
}
if (arg_idx == GARGCOUNT - 1)
arg_had_last = TRUE;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 0bf93ee001..09444ace0f 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -4771,6 +4771,10 @@ static void nv_ident(cmdarg_T *cap)
assert(*kp != NUL); // option.c:do_set() should default to ":help" if empty.
bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command
bool kp_help = (STRCMP(kp, ":he") == 0 || STRCMP(kp, ":help") == 0);
+ if (kp_help && *skipwhite(ptr) == NUL) {
+ EMSG(_(e_noident)); // found white space only
+ return;
+ }
size_t buf_size = n * 2 + 30 + STRLEN(kp);
char *buf = xmalloc(buf_size);
buf[0] = NUL;
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index a19e98725a..ba2f2ba969 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -82,6 +82,7 @@ struct qfline_S {
/// created using setqflist()/setloclist() with a title and/or user context
/// information and entries can be added later using setqflist()/setloclist().
typedef struct qf_list_S {
+ unsigned qf_id; ///< Unique identifier for this list
qfline_T *qf_start; ///< pointer to the first error
qfline_T *qf_last; ///< pointer to the last error
qfline_T *qf_ptr; ///< pointer to the current error
@@ -91,6 +92,14 @@ typedef struct qf_list_S {
char_u *qf_title; ///< title derived from the command that created
///< the error list or set by setqflist
typval_T *qf_ctx; ///< context set by setqflist/setloclist
+
+ struct dir_stack_T *qf_dir_stack;
+ char_u *qf_directory;
+ struct dir_stack_T *qf_file_stack;
+ char_u *qf_currfile;
+ bool qf_multiline;
+ bool qf_multiignore;
+ bool qf_multiscan;
} qf_list_T;
/// Quickfix/Location list stack definition
@@ -106,18 +115,10 @@ struct qf_info_S {
int qf_listcount; /* current number of lists */
int qf_curlist; /* current error list */
qf_list_T qf_lists[LISTCOUNT];
-
- int qf_dir_curlist; ///< error list for qf_dir_stack
- struct dir_stack_T *qf_dir_stack;
- char_u *qf_directory;
- struct dir_stack_T *qf_file_stack;
- char_u *qf_currfile;
- bool qf_multiline;
- bool qf_multiignore;
- bool qf_multiscan;
};
-static qf_info_T ql_info; /* global quickfix list */
+static qf_info_T ql_info; // global quickfix list
+static unsigned last_qf_id = 0; // Last Used quickfix list id
#define FMT_PATTERNS 10 /* maximum number of % recognized */
@@ -223,9 +224,8 @@ int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist,
qi = ll_get_or_alloc_list(wp);
}
- return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist,
- (linenr_T)0, (linenr_T)0,
- qf_title, enc);
+ return qf_init_ext(qi, qi->qf_curlist, efile, curbuf, NULL, errorformat,
+ newlist, (linenr_T)0, (linenr_T)0, qf_title, enc);
}
// Maximum number of bytes allowed per line while reading an errorfile.
@@ -712,8 +712,8 @@ static int qf_get_nextline(qfstate_T *state)
/// Parse a line and get the quickfix fields.
/// Return the QF_ status.
-static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen,
- efm_T *fmt_first, qffields_T *fields)
+static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf,
+ size_t linelen, efm_T *fmt_first, qffields_T *fields)
{
efm_T *fmt_ptr;
size_t len;
@@ -721,7 +721,7 @@ static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen,
int idx = 0;
char_u *tail = NULL;
regmatch_T regmatch;
-
+ qf_list_T *qfl = &qi->qf_lists[qf_idx];
// Always ignore case when looking for a matching error.
regmatch.rm_ic = true;
@@ -740,12 +740,12 @@ static int qf_parse_line(qf_info_T *qi, char_u *linebuf, size_t linelen,
restofline:
for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) {
idx = fmt_ptr->prefix;
- if (qi->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) {
+ if (qfl->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) {
continue;
}
fields->namebuf[0] = NUL;
fields->pattern[0] = NUL;
- if (!qi->qf_multiscan) {
+ if (!qfl->qf_multiscan) {
fields->errmsg[0] = NUL;
}
fields->lnum = 0;
@@ -759,7 +759,7 @@ restofline:
int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
fmt_ptr->prog = regmatch.regprog;
if (r) {
- if ((idx == 'C' || idx == 'Z') && !qi->qf_multiline) {
+ if ((idx == 'C' || idx == 'Z') && !qfl->qf_multiline) {
continue;
}
if (vim_strchr((char_u *)"EWI", idx) != NULL) {
@@ -809,7 +809,7 @@ restofline:
}
fields->type = *regmatch.startp[i];
}
- if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+
+ if (fmt_ptr->flags == '+' && !qfl->qf_multiscan) { // %+
if (linelen >= fields->errmsglen) {
// linelen + null terminator
fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
@@ -877,7 +877,7 @@ restofline:
break;
}
}
- qi->qf_multiscan = false;
+ qfl->qf_multiscan = false;
if (fmt_ptr == NULL || idx == 'D' || idx == 'X') {
if (fmt_ptr != NULL) {
@@ -886,13 +886,13 @@ restofline:
EMSG(_("E379: Missing or empty directory name"));
return QF_FAIL;
}
- qi->qf_directory = qf_push_dir(fields->namebuf, &qi->qf_dir_stack,
- false);
- if (qi->qf_directory == NULL) {
+ qfl->qf_directory = qf_push_dir(fields->namebuf, &qfl->qf_dir_stack,
+ false);
+ if (qfl->qf_directory == NULL) {
return QF_FAIL;
}
} else if (idx == 'X') { // leave directory
- qi->qf_directory = qf_pop_dir(&qi->qf_dir_stack);
+ qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack);
}
}
fields->namebuf[0] = NUL; // no match found, remove file name
@@ -906,7 +906,7 @@ restofline:
// copy whole line to error message
STRLCPY(fields->errmsg, linebuf, linelen + 1);
if (fmt_ptr == NULL) {
- qi->qf_multiline = qi->qf_multiignore = false;
+ qfl->qf_multiline = qfl->qf_multiignore = false;
}
} else {
// honor %> item
@@ -915,12 +915,12 @@ restofline:
}
if (vim_strchr((char_u *)"AEWI", idx) != NULL) {
- qi->qf_multiline = true; // start of a multi-line message
- qi->qf_multiignore = false; // reset continuation
+ qfl->qf_multiline = true; // start of a multi-line message
+ qfl->qf_multiignore = false; // reset continuation
} else if (vim_strchr((char_u *)"CZ", idx)
!= NULL) { // continuation of multi-line msg
- if (!qi->qf_multiignore) {
- qfline_T *qfprev = qi->qf_lists[qi->qf_curlist].qf_last;
+ if (!qfl->qf_multiignore) {
+ qfline_T *qfprev = qfl->qf_last;
if (qfprev == NULL) {
return QF_FAIL;
}
@@ -945,15 +945,15 @@ restofline:
}
qfprev->qf_viscol = fields->use_viscol;
if (!qfprev->qf_fnum) {
- qfprev->qf_fnum = qf_get_fnum(qi, qi->qf_directory,
- *fields->namebuf || qi->qf_directory
+ qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
+ *fields->namebuf || qfl->qf_directory
? fields->namebuf
- : qi->qf_currfile && fields->valid
- ? qi->qf_currfile : 0);
+ : qfl->qf_currfile && fields->valid
+ ? qfl->qf_currfile : 0);
}
}
if (idx == 'Z') {
- qi->qf_multiline = qi->qf_multiignore = false;
+ qfl->qf_multiline = qfl->qf_multiignore = false;
}
line_breakcheck();
@@ -963,23 +963,23 @@ restofline:
fields->valid = false;
if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) {
if (*fields->namebuf && idx == 'P') {
- qi->qf_currfile = qf_push_dir(fields->namebuf, &qi->qf_file_stack,
- true);
+ qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack,
+ true);
} else if (idx == 'Q') {
- qi->qf_currfile = qf_pop_dir(&qi->qf_file_stack);
+ qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack);
}
*fields->namebuf = NUL;
if (tail && *tail) {
STRMOVE(IObuff, skipwhite(tail));
- qi->qf_multiscan = true;
+ qfl->qf_multiscan = true;
goto restofline;
}
}
}
if (fmt_ptr->flags == '-') { // generally exclude this line
- if (qi->qf_multiline) {
+ if (qfl->qf_multiline) {
// also exclude continuation lines
- qi->qf_multiignore = true;
+ qfl->qf_multiignore = true;
}
return QF_IGNORE_LINE;
}
@@ -999,6 +999,7 @@ restofline:
static int
qf_init_ext(
qf_info_T *qi,
+ int qf_idx,
char_u *efile,
buf_T *buf,
typval_T *tv,
@@ -1041,17 +1042,20 @@ qf_init_ext(
goto qf_init_end;
}
- if (newlist || qi->qf_curlist == qi->qf_listcount) {
+ if (newlist || qf_idx == qi->qf_listcount) {
// make place for a new list
qf_new_list(qi, qf_title);
+ qf_idx = qi->qf_curlist;
} else {
// Adding to existing list, use last entry.
adding = true;
- if (qi->qf_lists[qi->qf_curlist].qf_count > 0) {
- old_last = qi->qf_lists[qi->qf_curlist].qf_last;
+ if (qi->qf_lists[qf_idx].qf_count > 0) {
+ old_last = qi->qf_lists[qf_idx].qf_last;
}
}
+ qf_list_T *qfl = &qi->qf_lists[qf_idx];
+
// Use the local value of 'errorformat' if it's set.
if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) {
efm = buf->b_p_efm;
@@ -1059,18 +1063,6 @@ qf_init_ext(
efm = errorformat;
}
- // If we are not adding or adding to another list: clear the state.
- if (newlist || qi->qf_curlist != qi->qf_dir_curlist) {
- qi->qf_dir_curlist = qi->qf_curlist;
- qf_clean_dir_stack(&qi->qf_dir_stack);
- qi->qf_directory = NULL;
- qf_clean_dir_stack(&qi->qf_file_stack);
- qi->qf_currfile = NULL;
- qi->qf_multiline = false;
- qi->qf_multiignore = false;
- qi->qf_multiscan = false;
- }
-
// If the errorformat didn't change between calls, then reuse the previously
// parsed values.
if (last_efm == NULL || (STRCMP(last_efm, efm) != 0)) {
@@ -1120,8 +1112,8 @@ qf_init_ext(
break;
}
- status = qf_parse_line(qi, state.linebuf, state.linelen, fmt_first,
- &fields);
+ status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen,
+ fmt_first, &fields);
if (status == QF_FAIL) {
goto error2;
}
@@ -1130,11 +1122,11 @@ qf_init_ext(
}
if (qf_add_entry(qi,
- qi->qf_curlist,
- qi->qf_directory,
- (*fields.namebuf || qi->qf_directory)
- ? fields.namebuf : ((qi->qf_currfile && fields.valid)
- ? qi->qf_currfile : (char_u *)NULL),
+ qf_idx,
+ qfl->qf_directory,
+ (*fields.namebuf || qfl->qf_directory)
+ ? fields.namebuf : ((qfl->qf_currfile && fields.valid)
+ ? qfl->qf_currfile : (char_u *)NULL),
0,
fields.errmsg,
fields.lnum,
@@ -1149,25 +1141,25 @@ qf_init_ext(
line_breakcheck();
}
if (state.fd == NULL || !ferror(state.fd)) {
- if (qi->qf_lists[qi->qf_curlist].qf_index == 0) {
- /* no valid entry found */
- qi->qf_lists[qi->qf_curlist].qf_ptr =
- qi->qf_lists[qi->qf_curlist].qf_start;
- qi->qf_lists[qi->qf_curlist].qf_index = 1;
- qi->qf_lists[qi->qf_curlist].qf_nonevalid = TRUE;
+ if (qfl->qf_index == 0) {
+ // no valid entry found
+ qfl->qf_ptr = qfl->qf_start;
+ qfl->qf_index = 1;
+ qfl->qf_nonevalid = true;
} else {
- qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
- if (qi->qf_lists[qi->qf_curlist].qf_ptr == NULL)
- qi->qf_lists[qi->qf_curlist].qf_ptr =
- qi->qf_lists[qi->qf_curlist].qf_start;
+ qfl->qf_nonevalid = false;
+ if (qfl->qf_ptr == NULL) {
+ qfl->qf_ptr = qfl->qf_start;
+ }
}
- /* return number of matches */
- retval = qi->qf_lists[qi->qf_curlist].qf_count;
+ // return number of matches
+ retval = qfl->qf_count;
goto qf_init_end;
}
EMSG(_(e_readerrf));
error2:
if (!adding) {
+ // Error when creating a new list. Free the new list
qf_free(qi, qi->qf_curlist);
qi->qf_listcount--;
if (qi->qf_curlist > 0) {
@@ -1183,7 +1175,9 @@ qf_init_end:
xfree(fields.pattern);
xfree(state.growbuf);
- qf_update_buffer(qi, old_last);
+ if (qf_idx == qi->qf_curlist) {
+ qf_update_buffer(qi, old_last);
+ }
if (state.vc.vc_type != CONV_NONE) {
convert_setup(&state.vc, NULL, NULL);
@@ -1206,9 +1200,9 @@ static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title)
}
}
-/*
- * Prepare for adding a new quickfix list.
- */
+// Prepare for adding a new quickfix list. If the current list is in the
+// middle of the stack, then all the following lists are freed and then
+// the new list is added.
static void qf_new_list(qf_info_T *qi, char_u *qf_title)
{
int i;
@@ -1232,6 +1226,7 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title)
qi->qf_curlist = qi->qf_listcount++;
memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T)));
qf_store_title(qi, qi->qf_curlist, qf_title);
+ qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id;
}
/*
@@ -1305,7 +1300,7 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
(qi == &ql_info) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
}
} else {
- qfp->qf_fnum = qf_get_fnum(qi, dir, fname);
+ qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname);
}
qfp->qf_text = vim_strsave(mesg);
qfp->qf_lnum = lnum;
@@ -1475,6 +1470,9 @@ void copy_loclist(win_T *from, win_T *to)
to_qfl->qf_index = from_qfl->qf_index; /* current index in the list */
+ // Assign a new ID for the location list
+ to_qfl->qf_id = ++last_qf_id;
+
/* When no valid entries are present in the list, qf_ptr points to
* the first item in the list */
if (to_qfl->qf_nonevalid) {
@@ -1488,7 +1486,8 @@ void copy_loclist(win_T *from, win_T *to)
// Get buffer number for file "directory/fname".
// Also sets the b_has_qf_entry flag.
-static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname)
+static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory,
+ char_u *fname)
{
char_u *ptr = NULL;
char_u *bufname;
@@ -1511,7 +1510,7 @@ static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname)
// directory change.
if (!os_path_exists(ptr)) {
xfree(ptr);
- directory = qf_guess_filepath(qi, fname);
+ directory = qf_guess_filepath(qi, qf_idx, fname);
if (directory) {
ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true);
} else {
@@ -1661,18 +1660,19 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
* Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb.
* qf_guess_filepath will return NULL.
*/
-static char_u *qf_guess_filepath(qf_info_T *qi, char_u *filename)
+static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename)
{
struct dir_stack_T *ds_ptr;
struct dir_stack_T *ds_tmp;
char_u *fullname;
+ qf_list_T *qfl = &qi->qf_lists[qf_idx];
// no dirs on the stack - there's nothing we can do
- if (qi->qf_dir_stack == NULL) {
+ if (qfl->qf_dir_stack == NULL) {
return NULL;
}
- ds_ptr = qi->qf_dir_stack->next;
+ ds_ptr = qfl->qf_dir_stack->next;
fullname = NULL;
while (ds_ptr) {
xfree(fullname);
@@ -1688,9 +1688,9 @@ static char_u *qf_guess_filepath(qf_info_T *qi, char_u *filename)
xfree(fullname);
// clean up all dirs we already left
- while (qi->qf_dir_stack->next != ds_ptr) {
- ds_tmp = qi->qf_dir_stack->next;
- qi->qf_dir_stack->next = qi->qf_dir_stack->next->next;
+ while (qfl->qf_dir_stack->next != ds_ptr) {
+ ds_tmp = qfl->qf_dir_stack->next;
+ qfl->qf_dir_stack->next = qfl->qf_dir_stack->next->next;
xfree(ds_tmp->dirname);
xfree(ds_tmp);
}
@@ -1845,12 +1845,12 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
/*
* For ":helpgrep" find a help window or open one.
*/
- if (qf_ptr->qf_type == 1 && (!curwin->w_buffer->b_help || cmdmod.tab != 0)) {
+ if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
win_T *wp = NULL;
if (cmdmod.tab == 0) {
FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
- if (wp2->w_buffer != NULL && wp2->w_buffer->b_help) {
+ if (bt_help(wp2->w_buffer)) {
wp = wp2;
break;
}
@@ -2415,11 +2415,12 @@ static void qf_free_items(qf_info_T *qi, int idx)
qfline_T *qfp;
qfline_T *qfpnext;
bool stop = false;
+ qf_list_T *qfl = &qi->qf_lists[idx];
- while (qi->qf_lists[idx].qf_count && qi->qf_lists[idx].qf_start != NULL) {
- qfp = qi->qf_lists[idx].qf_start;
+ while (qfl->qf_count && qfl->qf_start != NULL) {
+ qfp = qfl->qf_start;
qfpnext = qfp->qf_next;
- if (qi->qf_lists[idx].qf_title != NULL && !stop) {
+ if (!stop) {
xfree(qfp->qf_text);
stop = (qfp == qfpnext);
xfree(qfp->qf_pattern);
@@ -2428,40 +2429,42 @@ static void qf_free_items(qf_info_T *qi, int idx)
// Somehow qf_count may have an incorrect value, set it to 1
// to avoid crashing when it's wrong.
// TODO(vim): Avoid qf_count being incorrect.
- qi->qf_lists[idx].qf_count = 1;
+ qfl->qf_count = 1;
}
}
- qi->qf_lists[idx].qf_start = qfpnext;
- qi->qf_lists[idx].qf_count--;
- }
-
- qi->qf_lists[idx].qf_start = NULL;
- qi->qf_lists[idx].qf_ptr = NULL;
- qi->qf_lists[idx].qf_index = 0;
- qi->qf_lists[idx].qf_start = NULL;
- qi->qf_lists[idx].qf_last = NULL;
- qi->qf_lists[idx].qf_ptr = NULL;
- qi->qf_lists[idx].qf_nonevalid = true;
-
- qf_clean_dir_stack(&qi->qf_dir_stack);
- qi->qf_directory = NULL;
- qf_clean_dir_stack(&qi->qf_file_stack);
- qi->qf_currfile = NULL;
- qi->qf_multiline = false;
- qi->qf_multiignore = false;
- qi->qf_multiscan = false;
+ qfl->qf_start = qfpnext;
+ qfl->qf_count--;
+ }
+
+ qfl->qf_start = NULL;
+ qfl->qf_ptr = NULL;
+ qfl->qf_index = 0;
+ qfl->qf_start = NULL;
+ qfl->qf_last = NULL;
+ qfl->qf_ptr = NULL;
+ qfl->qf_nonevalid = true;
+
+ qf_clean_dir_stack(&qfl->qf_dir_stack);
+ qfl->qf_directory = NULL;
+ qf_clean_dir_stack(&qfl->qf_file_stack);
+ qfl->qf_currfile = NULL;
+ qfl->qf_multiline = false;
+ qfl->qf_multiignore = false;
+ qfl->qf_multiscan = false;
}
/// Free error list "idx". Frees all the entries in the quickfix list,
/// associated context information and the title.
static void qf_free(qf_info_T *qi, int idx)
{
+ qf_list_T *qfl = &qi->qf_lists[idx];
qf_free_items(qi, idx);
- xfree(qi->qf_lists[idx].qf_title);
- qi->qf_lists[idx].qf_title = NULL;
- tv_free(qi->qf_lists[idx].qf_ctx);
- qi->qf_lists[idx].qf_ctx = NULL;
+ xfree(qfl->qf_title);
+ qfl->qf_title = NULL;
+ tv_free(qfl->qf_ctx);
+ qfl->qf_ctx = NULL;
+ qfl->qf_id = 0;
}
/*
@@ -2601,8 +2604,9 @@ void ex_cclose(exarg_T *eap)
/* Find existing quickfix window and close it. */
win = qf_find_win(qi);
- if (win != NULL)
- win_close(win, FALSE);
+ if (win != NULL) {
+ win_close(win, false);
+ }
}
/*
@@ -4034,18 +4038,22 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start)
/// Add each quickfix error to list "list" as a dictionary.
/// If qf_idx is -1, use the current list. Otherwise, use the specified list.
-int get_errorlist(win_T *wp, int qf_idx, list_T *list)
+int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
{
- qf_info_T *qi = &ql_info;
+ const qf_info_T *qi = qi_arg;
char_u buf[2];
qfline_T *qfp;
int i;
int bufnum;
- if (wp != NULL) {
- qi = GET_LOC_LIST(wp);
- if (qi == NULL)
- return FAIL;
+ if (qi == NULL) {
+ qi = &ql_info;
+ if (wp != NULL) {
+ qi = GET_LOC_LIST(wp);
+ if (qi == NULL) {
+ return FAIL;
+ }
+ }
}
if (qf_idx == -1) {
@@ -4109,9 +4117,48 @@ enum {
QF_GETLIST_NR = 0x4,
QF_GETLIST_WINID = 0x8,
QF_GETLIST_CONTEXT = 0x10,
+ QF_GETLIST_ID = 0x20,
QF_GETLIST_ALL = 0xFF
};
+// Parse text from 'di' and return the quickfix list items
+static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
+{
+ int status = FAIL;
+ char_u *errorformat = p_efm;
+ dictitem_T *efm_di;
+
+ // Only a List value is supported
+ if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) {
+ // If errorformat is supplied then use it, otherwise use the 'efm'
+ // option setting
+ if ((efm_di = tv_dict_find(what, S_LEN("efm"))) != NULL) {
+ if (efm_di->di_tv.v_type != VAR_STRING
+ || efm_di->di_tv.vval.v_string == NULL) {
+ return FAIL;
+ }
+ errorformat = efm_di->di_tv.vval.v_string;
+ }
+
+ list_T *l = tv_list_alloc(kListLenMayKnow);
+ qf_info_T *qi = xmalloc(sizeof(*qi));
+ memset(qi, 0, sizeof(*qi));
+ qi->qf_refcount++;
+
+ if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat,
+ true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
+ (void)get_errorlist(qi, NULL, 0, l);
+ qf_free(qi, 0);
+ }
+ xfree(qi);
+
+ tv_dict_add_list(retdict, S_LEN("items"), l);
+ status = OK;
+ }
+
+ return status;
+}
+
/// Return quickfix/location list details (title) as a
/// dictionary. 'what' contains the details to return. If 'list_idx' is -1,
/// then current list is used. Otherwise the specified list is used.
@@ -4120,17 +4167,22 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
qf_info_T *qi = &ql_info;
dictitem_T *di;
+ if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
+ return qf_get_list_from_lines(what, di, retdict);
+ }
+
if (wp != NULL) {
qi = GET_LOC_LIST(wp);
- if (qi == NULL) {
- // If querying for the size of the location list, return 0
- if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL)
- && (di->di_tv.v_type == VAR_STRING)
- && strequal((const char *)di->di_tv.vval.v_string, "$")) {
- return tv_dict_add_nr(retdict, S_LEN("nr"), 0);
- }
- return FAIL;
+ }
+ // List is not present or is empty
+ if (qi == NULL || qi->qf_listcount == 0) {
+ // If querying for the size of the list, return 0
+ if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL)
+ && (di->di_tv.v_type == VAR_STRING)
+ && (STRCMP(di->di_tv.vval.v_string, "$") == 0)) {
+ return tv_dict_add_nr(retdict, S_LEN("nr"), 0);
}
+ return FAIL;
}
int status = OK;
@@ -4146,44 +4198,51 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
return FAIL;
}
- } else if (qi->qf_listcount == 0) { // stack is empty
- return FAIL;
}
- flags |= QF_GETLIST_NR;
} else if (di->di_tv.v_type == VAR_STRING
&& strequal((const char *)di->di_tv.vval.v_string, "$")) {
// Get the last quickfix list number
- if (qi->qf_listcount > 0) {
- qf_idx = qi->qf_listcount - 1;
- } else {
- qf_idx = -1; // Quickfix stack is empty
- }
- flags |= QF_GETLIST_NR;
+ qf_idx = qi->qf_listcount - 1;
} else {
return FAIL;
}
+ flags |= QF_GETLIST_NR;
}
- if (qf_idx != -1) {
- if (tv_dict_find(what, S_LEN("all")) != NULL) {
- flags |= QF_GETLIST_ALL;
- }
-
- if (tv_dict_find(what, S_LEN("title")) != NULL) {
- flags |= QF_GETLIST_TITLE;
- }
-
- if (tv_dict_find(what, S_LEN("winid")) != NULL) {
- flags |= QF_GETLIST_WINID;
- }
-
- if (tv_dict_find(what, S_LEN("context")) != NULL) {
- flags |= QF_GETLIST_CONTEXT;
+ if ((di = tv_dict_find(what, S_LEN("id"))) != NULL) {
+ // Look for a list with the specified id
+ if (di->di_tv.v_type == VAR_NUMBER) {
+ // For zero, use the current list or the list specifed by 'nr'
+ if (di->di_tv.vval.v_number != 0) {
+ for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) {
+ if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) {
+ break;
+ }
+ }
+ if (qf_idx == qi->qf_listcount) {
+ return FAIL; // List not found
+ }
+ }
+ flags |= QF_GETLIST_ID;
+ } else {
+ return FAIL;
}
+ }
- if (tv_dict_find(what, S_LEN("items")) != NULL) {
- flags |= QF_GETLIST_ITEMS;
- }
+ if (tv_dict_find(what, S_LEN("all")) != NULL) {
+ flags |= QF_GETLIST_ALL;
+ }
+ if (tv_dict_find(what, S_LEN("title")) != NULL) {
+ flags |= QF_GETLIST_TITLE;
+ }
+ if (tv_dict_find(what, S_LEN("winid")) != NULL) {
+ flags |= QF_GETLIST_WINID;
+ }
+ if (tv_dict_find(what, S_LEN("context")) != NULL) {
+ flags |= QF_GETLIST_CONTEXT;
+ }
+ if (tv_dict_find(what, S_LEN("items")) != NULL) {
+ flags |= QF_GETLIST_ITEMS;
}
if (flags & QF_GETLIST_TITLE) {
@@ -4204,7 +4263,7 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
list_T *l = tv_list_alloc(kListLenMayKnow);
- (void)get_errorlist(wp, qf_idx, l);
+ (void)get_errorlist(qi, NULL, qf_idx, l);
tv_dict_add_list(retdict, S_LEN("items"), l);
}
@@ -4221,6 +4280,10 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict)
}
}
+ if ((status == OK) && (flags & QF_GETLIST_ID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id);
+ }
+
return status;
}
@@ -4333,11 +4396,13 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
return retval;
}
-static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)
+static int qf_set_properties(qf_info_T *qi, dict_T *what, int action,
+ char_u *title)
{
dictitem_T *di;
int retval = FAIL;
int newlist = false;
+ char_u *errorformat = p_efm;
if (action == ' ' || qi->qf_curlist == qi->qf_listcount) {
newlist = true;
@@ -4353,25 +4418,48 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)
if ((action == ' ' || action == 'a') && qf_idx == qi->qf_listcount) {
// When creating a new list, accept qf_idx pointing to the next
- // non-available list
+ // non-available list and add the new list at the end of the
+ // stack.
newlist = true;
+ qf_idx = qi->qf_listcount - 1;
} else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
return FAIL;
- } else {
+ } else if (action != ' ') {
newlist = false; // use the specified list
}
} else if (di->di_tv.v_type == VAR_STRING
- && strequal((const char *)di->di_tv.vval.v_string, "$")
- && qi->qf_listcount > 0) {
- qf_idx = qi->qf_listcount - 1;
- newlist = false;
+ && strequal((const char *)di->di_tv.vval.v_string, "$")) {
+ if (qi->qf_listcount > 0) {
+ qf_idx = qi->qf_listcount - 1;
+ } else if (newlist) {
+ qf_idx = 0;
+ } else {
+ return FAIL;
+ }
+ } else {
+ return FAIL;
+ }
+ }
+
+ if (!newlist && (di = tv_dict_find(what, S_LEN("id"))) != NULL) {
+ // Use the quickfix/location list with the specified id
+ if (di->di_tv.v_type == VAR_NUMBER) {
+ for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) {
+ if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) {
+ break;
+ }
+ }
+ if (qf_idx == qi->qf_listcount) {
+ return FAIL; // List not found
+ }
} else {
return FAIL;
}
}
if (newlist) {
- qf_new_list(qi, NULL);
+ qi->qf_curlist = qf_idx;
+ qf_new_list(qi, title);
qf_idx = qi->qf_curlist;
}
@@ -4396,6 +4484,28 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action)
}
}
+ if ((di = tv_dict_find(what, S_LEN("efm"))) != NULL) {
+ if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL) {
+ return FAIL;
+ }
+ errorformat = di->di_tv.vval.v_string;
+ }
+
+ if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
+ // Only a List value is supported
+ if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) {
+ if (action == 'r') {
+ qf_free_items(qi, qf_idx);
+ }
+ if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
+ false, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
+ retval = OK;
+ }
+ } else {
+ return FAIL;
+ }
+ }
+
if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) {
tv_free(qi->qf_lists[qf_idx].qf_ctx);
@@ -4483,7 +4593,7 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
// Free the entire quickfix or location list stack
qf_free_stack(wp, qi);
} else if (what != NULL) {
- retval = qf_set_properties(qi, what, action);
+ retval = qf_set_properties(qi, what, action, title);
} else {
retval = qf_add_entries(qi, qi->qf_curlist, list, title, action);
}
@@ -4603,7 +4713,7 @@ void ex_cbuffer(exarg_T *eap)
qf_title = IObuff;
}
- if (qf_init_ext(qi, NULL, buf, NULL, p_efm,
+ if (qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
(eap->cmdidx != CMD_caddbuffer
&& eap->cmdidx != CMD_laddbuffer),
eap->line1, eap->line2, qf_title, NULL) > 0) {
@@ -4668,7 +4778,7 @@ void ex_cexpr(exarg_T *eap)
if (eval0(eap->arg, &tv, NULL, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
|| tv.v_type == VAR_LIST) {
- if (qf_init_ext(qi, NULL, NULL, &tv, p_efm,
+ if (qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm,
(eap->cmdidx != CMD_caddexpr
&& eap->cmdidx != CMD_laddexpr),
(linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) {
@@ -4725,16 +4835,26 @@ void ex_helpgrep(exarg_T *eap)
p_cpo = empty_option;
if (eap->cmdidx == CMD_lhelpgrep) {
- qi = NULL;
+ win_T *wp = NULL;
- /* Find an existing help window */
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer != NULL && wp->w_buffer->b_help) {
- qi = wp->w_llist;
+ // If the current window is a help window, then use it
+ if (bt_help(curwin->w_buffer)) {
+ wp = curwin;
+ } else {
+ // Find an existing help window
+ FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) {
+ if (bt_help(wp2->w_buffer)) {
+ wp = wp2;
+ break;
+ }
}
}
- /* Help window not found */
+ if (wp == NULL) { // Help window not found
+ qi = NULL;
+ } else {
+ qi = wp->w_llist;
+ }
if (qi == NULL) {
/* Allocate a new location list for help text matches */
qi = ll_new_list();
@@ -4855,11 +4975,13 @@ void ex_helpgrep(exarg_T *eap)
if (eap->cmdidx == CMD_lhelpgrep) {
/* If the help window is not opened or if it already points to the
* correct location list, then free the new location list. */
- if (!curwin->w_buffer->b_help || curwin->w_llist == qi) {
- if (new_qi)
+ if (!bt_help(curwin->w_buffer) || curwin->w_llist == qi) {
+ if (new_qi) {
ll_free_all(&qi);
- } else if (curwin->w_llist == NULL)
+ }
+ } else if (curwin->w_llist == NULL) {
curwin->w_llist = qi;
+ }
}
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 59e0ec2193..ca57148431 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -3912,23 +3912,16 @@ win_line (
}
}
- /*
- * At end of the text line.
- */
+ //
+ // At end of the text line.
+ //
if (c == NUL) {
- if (eol_hl_off > 0 && vcol - eol_hl_off == (long)wp->w_virtcol
- && lnum == wp->w_cursor.lnum) {
- /* highlight last char after line */
- --col;
- --off;
- --vcol;
- }
-
- /* Highlight 'cursorcolumn' & 'colorcolumn' past end of the line. */
- if (wp->w_p_wrap)
+ // Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
+ if (wp->w_p_wrap) {
v = wp->w_skipcol;
- else
+ } else {
v = wp->w_leftcol;
+ }
/* check if line ends before left margin */
if (vcol < v + col - win_col_off(wp))
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 2a980af2a2..54090afd71 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -2608,11 +2608,11 @@ static int jumpto_tag(
win_enter(curwin_save, true);
}
- --RedrawingDisabled;
+ RedrawingDisabled--;
} else {
- --RedrawingDisabled;
- if (postponed_split) { /* close the window */
- win_close(curwin, FALSE);
+ RedrawingDisabled--;
+ if (postponed_split) { // close the window
+ win_close(curwin, false);
postponed_split = 0;
}
}
diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim
new file mode 100644
index 0000000000..8f02fd29e3
--- /dev/null
+++ b/src/nvim/testdir/test_exit.vim
@@ -0,0 +1,57 @@
+" Tests for exiting Vim.
+
+source shared.vim
+
+func Test_exiting()
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'quit',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'help',
+ \ 'wincmd w',
+ \ 'quit',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'split',
+ \ 'new',
+ \ 'qall',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+
+ let after = [
+ \ 'au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")',
+ \ 'au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")',
+ \ 'augroup nasty',
+ \ ' au ExitPre * split',
+ \ 'augroup END',
+ \ 'quit',
+ \ 'augroup nasty',
+ \ ' au! ExitPre',
+ \ 'augroup END',
+ \ 'quit',
+ \ ]
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre', 'QuitPre', 'ExitPre'],
+ \ readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index 26edc16107..ed3181564c 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -13,4 +13,40 @@ endfunc
func Test_help_errors()
call assert_fails('help doesnotexist', 'E149:')
call assert_fails('help!', 'E478:')
+
+ new
+ set keywordprg=:help
+ call setline(1, " ")
+ call assert_fails('normal VK', 'E349:')
+ bwipe!
+endfunc
+
+func Test_help_keyword()
+ new
+ set keywordprg=:help
+ call setline(1, " Visual ")
+ normal VK
+ call assert_match('^Visual mode', getline('.'))
+ call assert_equal('help', &ft)
+ close
+ bwipe!
+endfunc
+
+func Test_help_local_additions()
+ call mkdir('Xruntime/doc', 'p')
+ call writefile(['*mydoc.txt* my awesome doc'], 'Xruntime/doc/mydoc.txt')
+ call writefile(['*mydoc-ext.txt* my extended awesome doc'], 'Xruntime/doc/mydoc-ext.txt')
+ let rtp_save = &rtp
+ set rtp+=./Xruntime
+ help
+ 1
+ call search('mydoc.txt')
+ call assert_equal('|mydoc.txt| my awesome doc', getline('.'))
+ 1
+ call search('mydoc-ext.txt')
+ call assert_equal('|mydoc-ext.txt| my extended awesome doc', getline('.'))
+ close
+
+ call delete('Xruntime', 'rf')
+ let &rtp = rtp_save
endfunc
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index 4d4a902031..c873487b92 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -38,6 +38,34 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*:?\*')
helpclose
+ help q?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*q?\*')
+ call assert_true(expand('<cword>') == 'q?')
+ helpclose
+
+ help -?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*-?\*')
+ helpclose
+
+ help v_g?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*v_g?\*')
+ helpclose
+
+ help expr-!=?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*expr-!=?\*')
+ call assert_true(expand('<cword>') == 'expr-!=?')
+ helpclose
+
+ help expr-isnot?
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*expr-isnot?\*')
+ call assert_true(expand('<cword>') == 'expr-isnot?')
+ helpclose
+
help FileW*Post
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*FileWritePost\*')
diff --git a/src/nvim/testdir/test_hlsearch.vim b/src/nvim/testdir/test_hlsearch.vim
index 066fdd0250..1fc7b04f88 100644
--- a/src/nvim/testdir/test_hlsearch.vim
+++ b/src/nvim/testdir/test_hlsearch.vim
@@ -4,7 +4,6 @@ function! Test_hlsearch()
new
call setline(1, repeat(['aaa'], 10))
set hlsearch nolazyredraw
- let r=[]
" redraw is needed to make hlsearch highlight the matches
exe "normal! /aaa\<CR>" | redraw
let r1 = screenattr(1, 1)
@@ -32,3 +31,16 @@ function! Test_hlsearch()
call getchar(1)
enew!
endfunction
+
+func Test_hlsearch_eol_highlight()
+ new
+ call append(1, repeat([''], 9))
+ set hlsearch nolazyredraw
+ exe "normal! /$\<CR>" | redraw
+ let attr = screenattr(1, 1)
+ for row in range(2, 10)
+ call assert_equal(attr, screenattr(row, 1), 'in line ' . row)
+ endfor
+ set nohlsearch
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 4774cf4af5..9ba264deb6 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -19,7 +19,8 @@ func Test_mksession()
\ 'two tabs in one line',
\ 'one ä multibyteCharacter',
\ 'aä Ä two multiByte characters',
- \ 'Aäöü three mulTibyte characters'
+ \ 'Aäöü three mulTibyte characters',
+ \ 'short line',
\ ])
let tmpfile = 'Xtemp'
exec 'w! ' . tmpfile
@@ -41,6 +42,8 @@ func Test_mksession()
norm! j16|
split
norm! j16|
+ split
+ norm! j$
wincmd l
set nowrap
@@ -63,7 +66,7 @@ func Test_mksession()
split
call wincol()
mksession! Xtest_mks.out
- let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "\\(^ *normal! 0\\|^ *exe ''normal!\\)"')
+ let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "\\(^ *normal! [0$]\\|^ *exe ''normal!\\)"')
let expected = [
\ 'normal! 016|',
\ 'normal! 016|',
@@ -73,6 +76,7 @@ func Test_mksession()
\ 'normal! 016|',
\ 'normal! 016|',
\ 'normal! 016|',
+ \ 'normal! $',
\ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
\ " normal! 016|",
\ " exe 'normal! ' . s:c . '|zs' . 16 . '|'",
@@ -151,5 +155,87 @@ func Test_mksession_one_buffer_two_windows()
call delete('Xtest_mks.out')
endfunc
+" Test :mkview with a file argument.
+func Test_mkview_file()
+ " Create a view with line number and a fold.
+ help :mkview
+ set number
+ norm! V}zf0
+ let pos = getpos('.')
+ let linefoldclosed1 = foldclosed('.')
+ mkview! Xview
+ set nonumber
+ norm! zrj
+ " We can close the help window, as mkview with a file name should
+ " generate a command to edit the file.
+ helpclose
+
+ source Xview
+ call assert_equal(1, &number)
+ call assert_match('\*:mkview\*$', getline('.'))
+ call assert_equal(pos, getpos('.'))
+ call assert_equal(linefoldclosed1, foldclosed('.'))
+
+ " Creating a view again with the same file name should fail (file
+ " already exists). But with a !, the previous view should be
+ " overwritten without error.
+ help :loadview
+ call assert_fails('mkview Xview', 'E189:')
+ call assert_match('\*:loadview\*$', getline('.'))
+ mkview! Xview
+ call assert_match('\*:loadview\*$', getline('.'))
+
+ call delete('Xview')
+ bwipe
+endfunc
+
+" Test :mkview and :loadview with a custom 'viewdir'.
+func Test_mkview_loadview_with_viewdir()
+ set viewdir=Xviewdir
+
+ help :mkview
+ set number
+ norm! V}zf
+ let pos = getpos('.')
+ let linefoldclosed1 = foldclosed('.')
+ mkview 1
+ set nonumber
+ norm! zrj
+
+ loadview 1
+
+ " The directory Xviewdir/ should have been created and the view
+ " should be stored in that directory.
+ let pathsep = has('win32') ? '\' : '/'
+ call assert_equal('Xviewdir' . pathsep .
+ \ substitute(
+ \ substitute(
+ \ expand('%:p'), pathsep, '=+', 'g'), ':', '=-', 'g') . '=1.vim',
+ \ glob('Xviewdir/*'))
+ call assert_equal(1, &number)
+ call assert_match('\*:mkview\*$', getline('.'))
+ call assert_equal(pos, getpos('.'))
+ call assert_equal(linefoldclosed1, foldclosed('.'))
+
+ call delete('Xviewdir', 'rf')
+ set viewdir&
+ helpclose
+endfunc
+
+func Test_mkview_no_file_name()
+ new
+ " :mkview or :mkview {nr} should fail in a unnamed buffer.
+ call assert_fails('mkview', 'E32:')
+ call assert_fails('mkview 1', 'E32:')
+
+ " :mkview {file} should succeed in a unnamed buffer.
+ mkview Xview
+ help
+ source Xview
+ call assert_equal('', bufname('%'))
+
+ call delete('Xview')
+ %bwipe
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 33abb69ca6..7a53db7605 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -11,7 +11,7 @@ func s:setup_commands(cchar)
command! -nargs=* -bang Xlist <mods>clist<bang> <args>
command! -nargs=* Xgetexpr <mods>cgetexpr <args>
command! -nargs=* Xaddexpr <mods>caddexpr <args>
- command! -nargs=* Xolder <mods>colder <args>
+ command! -nargs=* -count Xolder <mods><count>colder <args>
command! -nargs=* Xnewer <mods>cnewer <args>
command! -nargs=* Xopen <mods>copen <args>
command! -nargs=* Xwindow <mods>cwindow <args>
@@ -43,7 +43,7 @@ func s:setup_commands(cchar)
command! -nargs=* -bang Xlist <mods>llist<bang> <args>
command! -nargs=* Xgetexpr <mods>lgetexpr <args>
command! -nargs=* Xaddexpr <mods>laddexpr <args>
- command! -nargs=* Xolder <mods>lolder <args>
+ command! -nargs=* -count Xolder <mods><count>lolder <args>
command! -nargs=* Xnewer <mods>lnewer <args>
command! -nargs=* Xopen <mods>lopen <args>
command! -nargs=* Xwindow <mods>lwindow <args>
@@ -1727,7 +1727,7 @@ func Xproperty_tests(cchar)
call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title)
" Changing the title of an earlier quickfix list
- call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2})
+ call g:Xsetlist([], 'r', {'title' : 'NewTitle', 'nr' : 2})
call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title)
" Changing the title of an invalid quickfix list
@@ -1794,10 +1794,10 @@ func Xproperty_tests(cchar)
Xexpr "One"
Xexpr "Two"
Xexpr "Three"
- call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1})
- call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2})
+ call g:Xsetlist([], 'r', {'context' : [1], 'nr' : 1})
+ call g:Xsetlist([], 'a', {'context' : [2], 'nr' : 2})
" Also, check for setting the context using quickfix list number zero.
- call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0})
+ call g:Xsetlist([], 'r', {'context' : [3], 'nr' : 0})
call test_garbagecollect_now()
let l = g:Xgetlist({'nr' : 1, 'context' : 1})
call assert_equal([1], l.context)
@@ -1844,6 +1844,11 @@ func Xproperty_tests(cchar)
let l = g:Xgetlist({'items':1})
call assert_equal(0, len(l.items))
+ " The following used to crash Vim with address sanitizer
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]})
+ call assert_equal(10, g:Xgetlist({'items':1}).items[0].lnum)
+
" Save and restore the quickfix stack
call g:Xsetlist([], 'f')
call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
@@ -1874,8 +1879,9 @@ func Xproperty_tests(cchar)
call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']})
let l1=g:Xgetlist({'nr':1,'all':1})
let l2=g:Xgetlist({'nr':2,'all':1})
- let l1.nr=2
- let l2.nr=1
+ let save_id = l1.id
+ let l1.id=l2.id
+ let l2.id=save_id
call g:Xsetlist([], 'r', l1)
call g:Xsetlist([], 'r', l2)
let newl1=g:Xgetlist({'nr':1,'all':1})
@@ -2266,6 +2272,315 @@ func Xchangedtick_tests(cchar)
endfunc
func Test_changedtick()
- call Xchangedtick_tests('c')
- call Xchangedtick_tests('l')
+ call Xchangedtick_tests('c')
+ call Xchangedtick_tests('l')
+endfunc
+
+" Tests for parsing an expression using setqflist()
+func Xsetexpr_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ let t = ["File1:10:Line10", "File1:20:Line20"]
+ call g:Xsetlist([], ' ', {'lines' : t})
+ call g:Xsetlist([], 'a', {'lines' : ["File1:30:Line30"]})
+
+ let l = g:Xgetlist()
+ call assert_equal(3, len(l))
+ call assert_equal(20, l[1].lnum)
+ call assert_equal('Line30', l[2].text)
+ call g:Xsetlist([], 'r', {'lines' : ["File2:5:Line5"]})
+ let l = g:Xgetlist()
+ call assert_equal(1, len(l))
+ call assert_equal('Line5', l[0].text)
+ call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : 10}))
+ call assert_equal(-1, g:Xsetlist([], 'a', {'lines' : "F1:10:L10"}))
+
+ call g:Xsetlist([], 'f')
+ " Add entries to multiple lists
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:10:Line10"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:20:Line20"]})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["File1:15:Line15"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["File2:25:Line25"]})
+ call assert_equal('Line15', g:Xgetlist({'nr':1, 'items':1}).items[1].text)
+ call assert_equal('Line25', g:Xgetlist({'nr':2, 'items':1}).items[1].text)
+
+ " Adding entries using a custom efm
+ set efm&
+ call g:Xsetlist([], ' ', {'efm' : '%f#%l#%m',
+ \ 'lines' : ["F1#10#L10", "F2#20#L20"]})
+ call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum)
+ call g:Xsetlist([], 'a', {'efm' : '%f#%l#%m', 'lines' : ["F3:30:L30"]})
+ call assert_equal('F3:30:L30', g:Xgetlist({'items':1}).items[2].text)
+ call assert_equal(20, g:Xgetlist({'items':1}).items[1].lnum)
+ call assert_equal(-1, g:Xsetlist([], 'a', {'efm' : [],
+ \ 'lines' : ['F1:10:L10']}))
+endfunc
+
+func Test_setexpr()
+ call Xsetexpr_tests('c')
+ call Xsetexpr_tests('l')
+endfunc
+
+" Tests for per quickfix/location list directory stack
+func Xmultidirstack_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ Xexpr "" | Xexpr ""
+
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["Entering dir 'Xone/a'"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["Entering dir 'Xtwo/a'"]})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["one.txt:3:one one one"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["two.txt:5:two two two"]})
+
+ let l1 = g:Xgetlist({'nr':1, 'items':1})
+ let l2 = g:Xgetlist({'nr':2, 'items':1})
+ call assert_equal(expand('Xone/a/one.txt'), bufname(l1.items[1].bufnr))
+ call assert_equal(3, l1.items[1].lnum)
+ call assert_equal(expand('Xtwo/a/two.txt'), bufname(l2.items[1].bufnr))
+ call assert_equal(5, l2.items[1].lnum)
+endfunc
+
+func Test_multidirstack()
+ call mkdir('Xone/a', 'p')
+ call mkdir('Xtwo/a', 'p')
+ let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
+ call writefile(lines, 'Xone/a/one.txt')
+ call writefile(lines, 'Xtwo/a/two.txt')
+ let save_efm = &efm
+ set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f'
+
+ call Xmultidirstack_tests('c')
+ call Xmultidirstack_tests('l')
+
+ let &efm = save_efm
+ call delete('Xone', 'rf')
+ call delete('Xtwo', 'rf')
+endfunc
+
+" Tests for per quickfix/location list file stack
+func Xmultifilestack_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ Xexpr "" | Xexpr ""
+
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["[one.txt]"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["[two.txt]"]})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'lines' : ["(3,5) one one one"]})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'lines' : ["(5,9) two two two"]})
+
+ let l1 = g:Xgetlist({'nr':1, 'items':1})
+ let l2 = g:Xgetlist({'nr':2, 'items':1})
+ call assert_equal('one.txt', bufname(l1.items[1].bufnr))
+ call assert_equal(3, l1.items[1].lnum)
+ call assert_equal('two.txt', bufname(l2.items[1].bufnr))
+ call assert_equal(5, l2.items[1].lnum)
+endfunc
+
+func Test_multifilestack()
+ let lines = ['1', '2', 'one one one', '4', 'two two two', '6', '7']
+ call writefile(lines, 'one.txt')
+ call writefile(lines, 'two.txt')
+ let save_efm = &efm
+ set efm=%+P[%f],(%l\\,%c)\ %m,%-Q
+
+ call Xmultifilestack_tests('c')
+ call Xmultifilestack_tests('l')
+
+ let &efm = save_efm
+ call delete('one.txt')
+ call delete('two.txt')
+endfunc
+
+" Tests for per buffer 'efm' setting
+func Test_perbuf_efm()
+ call writefile(["File1-10-Line10"], 'one.txt')
+ call writefile(["File2#20#Line20"], 'two.txt')
+ set efm=%f#%l#%m
+ new | only
+ new
+ setlocal efm=%f-%l-%m
+ cfile one.txt
+ wincmd w
+ caddfile two.txt
+
+ let l = getqflist()
+ call assert_equal(10, l[0].lnum)
+ call assert_equal('Line20', l[1].text)
+
+ set efm&
+ new | only
+ call delete('one.txt')
+ call delete('two.txt')
+endfunc
+
+" Open multiple help windows using ":lhelpgrep
+" This test used to crash Vim
+func Test_Multi_LL_Help()
+ new | only
+ lhelpgrep window
+ lopen
+ e#
+ lhelpgrep buffer
+ call assert_equal(3, winnr('$'))
+ call assert_true(len(getloclist(1)) != 0)
+ call assert_true(len(getloclist(2)) != 0)
+ new | only
+endfunc
+
+" Tests for adding new quickfix lists using setqflist()
+func XaddQf_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Create a new list using ' ' for action
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], ' ', {'title' : 'Test1'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test1', l.title)
+
+ " Create a new list using ' ' for action and '$' for 'nr'
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], ' ', {'title' : 'Test2', 'nr' : '$'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test2', l.title)
+
+ " Create a new list using 'a' for action
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], 'a', {'title' : 'Test3'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test3', l.title)
+
+ " Create a new list using 'a' for action and '$' for 'nr'
+ call g:Xsetlist([], 'f')
+ call g:Xsetlist([], 'a', {'title' : 'Test3', 'nr' : '$'})
+ call g:Xsetlist([], 'a', {'title' : 'Test4'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(1, l.nr)
+ call assert_equal('Test4', l.title)
+
+ " Adding a quickfix list should remove all the lists following the current
+ " list.
+ Xexpr "" | Xexpr "" | Xexpr ""
+ silent! 10Xolder
+ call g:Xsetlist([], ' ', {'title' : 'Test5'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(2, l.nr)
+ call assert_equal('Test5', l.title)
+
+ " Add a quickfix list using '$' as the list number.
+ let lastqf = g:Xgetlist({'nr':'$'}).nr
+ silent! 99Xolder
+ call g:Xsetlist([], ' ', {'nr' : '$', 'title' : 'Test6'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(lastqf + 1, l.nr)
+ call assert_equal('Test6', l.title)
+
+ " Add a quickfix list using 'nr' set to one more than the quickfix
+ " list size.
+ let lastqf = g:Xgetlist({'nr':'$'}).nr
+ silent! 99Xolder
+ call g:Xsetlist([], ' ', {'nr' : lastqf + 1, 'title' : 'Test7'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(lastqf + 1, l.nr)
+ call assert_equal('Test7', l.title)
+
+ " Add a quickfix list to a stack with 10 lists using 'nr' set to '$'
+ exe repeat('Xexpr "" |', 9) . 'Xexpr ""'
+ silent! 99Xolder
+ call g:Xsetlist([], ' ', {'nr' : '$', 'title' : 'Test8'})
+ let l = g:Xgetlist({'nr' : '$', 'all' : 1})
+ call assert_equal(10, l.nr)
+ call assert_equal('Test8', l.title)
+
+ " Add a quickfix list using 'nr' set to a value greater than 10
+ call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : 12, 'title' : 'Test9'}))
+
+ " Try adding a quickfix list with 'nr' set to a value greater than the
+ " quickfix list size but less than 10.
+ call g:Xsetlist([], 'f')
+ Xexpr "" | Xexpr "" | Xexpr ""
+ silent! 99Xolder
+ call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : 8, 'title' : 'Test10'}))
+
+ " Add a quickfix list using 'nr' set to a some string or list
+ call assert_equal(-1, g:Xsetlist([], ' ', {'nr' : [1,2], 'title' : 'Test11'}))
+endfunc
+
+func Test_add_qf()
+ call XaddQf_tests('c')
+ call XaddQf_tests('l')
+endfunc
+
+" Test for getting the quickfix list items from some text without modifying
+" the quickfix stack
+func XgetListFromLines(cchar)
+ call s:setup_commands(a:cchar)
+ call g:Xsetlist([], 'f')
+
+ let l = g:Xgetlist({'lines' : ["File2:20:Line20", "File2:30:Line30"]}).items
+ call assert_equal(2, len(l))
+ call assert_equal(30, l[1].lnum)
+
+ call assert_equal({}, g:Xgetlist({'lines' : 10}))
+ call assert_equal({}, g:Xgetlist({'lines' : 'File1:10:Line10'}))
+ call assert_equal([], g:Xgetlist({'lines' : []}).items)
+ call assert_equal([], g:Xgetlist({'lines' : [10, 20]}).items)
+
+ " Parse text using a custom efm
+ set efm&
+ let l = g:Xgetlist({'lines':['File3#30#Line30'], 'efm' : '%f#%l#%m'}).items
+ call assert_equal('Line30', l[0].text)
+ let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : '%f-%l-%m'}).items
+ call assert_equal('File3:30:Line30', l[0].text)
+ let l = g:Xgetlist({'lines':['File3:30:Line30'], 'efm' : [1,2]})
+ call assert_equal({}, l)
+ call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':'%2'})", 'E376:')
+ call assert_fails("call g:Xgetlist({'lines':['abc'], 'efm':''})", 'E378:')
+
+ " Make sure that the quickfix stack is not modified
+ call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr)
+endfunc
+
+func Test_get_list_from_lines()
+ call XgetListFromLines('c')
+ call XgetListFromLines('l')
+endfunc
+
+" Tests for the quickfix list id
+func Xqfid_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+ call assert_equal({}, g:Xgetlist({'id':0}))
+ Xexpr ''
+ let start_id = g:Xgetlist({'id' : 0}).id
+ Xexpr '' | Xexpr ''
+ Xolder
+ call assert_equal(start_id, g:Xgetlist({'id':0, 'nr':1}).id)
+ call assert_equal(start_id + 1, g:Xgetlist({'id':0, 'nr':0}).id)
+ call assert_equal(start_id + 2, g:Xgetlist({'id':0, 'nr':'$'}).id)
+ call assert_equal({}, g:Xgetlist({'id':0, 'nr':99}))
+ call assert_equal(2, g:Xgetlist({'id':start_id + 1, 'nr':0}).nr)
+ call assert_equal({}, g:Xgetlist({'id':99, 'nr':0}))
+ call assert_equal({}, g:Xgetlist({'id':"abc", 'nr':0}))
+
+ call g:Xsetlist([], 'a', {'id':start_id, 'context':[1,2]})
+ call assert_equal([1,2], g:Xgetlist({'nr':1, 'context':1}).context)
+ call g:Xsetlist([], 'a', {'id':start_id+1, 'lines':['F1:10:L10']})
+ call assert_equal('L10', g:Xgetlist({'nr':2, 'items':1}).items[0].text)
+ call assert_equal(-1, g:Xsetlist([], 'a', {'id':999, 'title':'Vim'}))
+ call assert_equal(-1, g:Xsetlist([], 'a', {'id':'abc', 'title':'Vim'}))
+
+ let qfid = g:Xgetlist({'id':0, 'nr':0})
+ call g:Xsetlist([], 'f')
+ call assert_equal({}, g:Xgetlist({'id':qfid, 'nr':0}))
+endfunc
+
+func Test_qf_id()
+ call Xqfid_tests('c')
+ call Xqfid_tests('l')
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index 81ac2b6171..6450bf02e8 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -169,5 +169,25 @@ func Test_stop_all_in_callback()
call assert_equal(0, len(info))
endfunc
+func FeedAndPeek(timer)
+ call test_feedinput('a')
+ call getchar(1)
+endfunc
+
+func Interrupt(timer)
+ call test_feedinput("\<C-C>")
+endfunc
+
+func Test_peek_and_get_char()
+ throw 'skipped: Nvim does not support test_feedinput()'
+ if !has('unix') && !has('gui_running')
+ return
+ endif
+ call timer_start(0, 'FeedAndPeek')
+ let intr = timer_start(100, 'Interrupt')
+ let c = getchar()
+ call assert_equal(char2nr('a'), c)
+ call timer_stop(intr)
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 8b031646b5..06f9d03554 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -31,3 +31,85 @@ func Test_writefile_fails_gently()
call assert_fails('call writefile([], [])', 'E730:')
endfunc
+
+func SetFlag(timer)
+ let g:flag = 1
+endfunc
+
+func Test_write_quit_split()
+ " Prevent exiting by splitting window on file write.
+ augroup testgroup
+ autocmd BufWritePre * split
+ augroup END
+ e! Xfile
+ call setline(1, 'nothing')
+ wq
+
+ if has('timers')
+ " timer will not run if "exiting" is still set
+ let g:flag = 0
+ call timer_start(1, 'SetFlag')
+ sleep 50m
+ call assert_equal(1, g:flag)
+ unlet g:flag
+ endif
+ au! testgroup
+ bwipe Xfile
+ call delete('Xfile')
+endfunc
+
+func Test_nowrite_quit_split()
+ " Prevent exiting by opening a help window.
+ e! Xfile
+ help
+ wincmd w
+ exe winnr() . 'q'
+
+ if has('timers')
+ " timer will not run if "exiting" is still set
+ let g:flag = 0
+ call timer_start(1, 'SetFlag')
+ sleep 50m
+ call assert_equal(1, g:flag)
+ unlet g:flag
+ endif
+ bwipe Xfile
+endfunc
+
+func Test_writefile_autowrite()
+ set autowrite
+ new
+ next Xa Xb Xc
+ call setline(1, 'aaa')
+ next
+ call assert_equal(['aaa'], readfile('Xa'))
+ call setline(1, 'bbb')
+ call assert_fails('edit XX')
+ call assert_false(filereadable('Xb'))
+
+ set autowriteall
+ edit XX
+ call assert_equal(['bbb'], readfile('Xb'))
+
+ bwipe!
+ call delete('Xa')
+ call delete('Xb')
+ set noautowrite
+endfunc
+
+func Test_writefile_autowrite_nowrite()
+ set autowrite
+ new
+ next Xa Xb Xc
+ set buftype=nowrite
+ call setline(1, 'aaa')
+ let buf = bufnr('%')
+ " buffer contents silently lost
+ edit XX
+ call assert_false(filereadable('Xa'))
+ rewind
+ call assert_equal('', getline(1))
+
+ bwipe!
+ set noautowrite
+endfunc
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 9976ae9aff..6f382acd84 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1859,20 +1859,18 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf,
return true;
}
-/*
- * Close window "win". Only works for the current tab page.
- * If "free_buf" is TRUE related buffer may be unloaded.
- *
- * Called by :quit, :close, :xit, :wq and findtag().
- * Returns FAIL when the window was not closed.
- */
-int win_close(win_T *win, int free_buf)
+// Close window "win". Only works for the current tab page.
+// If "free_buf" is true related buffer may be unloaded.
+//
+// Called by :quit, :close, :xit, :wq and findtag().
+// Returns FAIL when the window was not closed.
+int win_close(win_T *win, bool free_buf)
{
win_T *wp;
int other_buffer = FALSE;
int close_curwin = FALSE;
int dir;
- int help_window = FALSE;
+ bool help_window = false;
tabpage_T *prev_curtab = curtab;
frame_T *win_frame = win->w_frame->fr_parent;
@@ -1902,10 +1900,11 @@ int win_close(win_T *win, int free_buf)
/* When closing the help window, try restoring a snapshot after closing
* the window. Otherwise clear the snapshot, it's now invalid. */
- if (win->w_buffer != NULL && win->w_buffer->b_help)
- help_window = TRUE;
- else
+ if (bt_help(win->w_buffer)) {
+ help_window = true;
+ } else {
clear_snapshot(curtab, SNAP_HELP_IDX);
+ }
if (win == curwin) {
/*
@@ -1967,10 +1966,11 @@ int win_close(win_T *win, int free_buf)
if (only_one_window() && win_valid(win) && win->w_buffer == NULL
&& (last_window() || curtab != prev_curtab
|| close_last_window_tabpage(win, free_buf, prev_curtab))) {
- /* Autocommands have close all windows, quit now. Restore
- * curwin->w_buffer, otherwise writing ShaDa file may fail. */
- if (curwin->w_buffer == NULL)
+ // Autocommands have closed all windows, quit now. Restore
+ // curwin->w_buffer, otherwise writing ShaDa file may fail.
+ if (curwin->w_buffer == NULL) {
curwin->w_buffer = curbuf;
+ }
getout(0);
}
// Autocommands may have moved to another tab page.
@@ -5341,7 +5341,7 @@ bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
int count = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer != NULL
- && (!((wp->w_buffer->b_help && !curbuf->b_help)
+ && (!((bt_help(wp->w_buffer) && !bt_help(curbuf))
|| wp->w_p_pvw) || wp == curwin) && wp != aucmd_win) {
count++;
}