aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c57
-rw-r--r--src/nvim/api/private/helpers.c226
-rw-r--r--src/nvim/api/private/helpers.h1
-rw-r--r--src/nvim/api/ui.c31
-rw-r--r--src/nvim/api/vim.c66
-rw-r--r--src/nvim/buffer.c9
-rw-r--r--src/nvim/buffer_defs.h70
-rw-r--r--src/nvim/eval.c225
-rw-r--r--src/nvim/eval.lua6
-rw-r--r--src/nvim/eval/typval.c28
-rw-r--r--src/nvim/event/rstream.c10
-rw-r--r--src/nvim/event/stream.c10
-rw-r--r--src/nvim/event/stream.h3
-rw-r--r--src/nvim/ex_cmds.c41
-rw-r--r--src/nvim/ex_cmds2.c35
-rw-r--r--src/nvim/ex_docmd.c18
-rw-r--r--src/nvim/ex_getln.c9
-rw-r--r--src/nvim/file_search.c9
-rw-r--r--src/nvim/fileio.c190
-rw-r--r--src/nvim/getchar.c725
-rw-r--r--src/nvim/getchar.h34
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/main.c7
-rw-r--r--src/nvim/memline.c268
-rw-r--r--src/nvim/menu.c11
-rw-r--r--src/nvim/message.c15
-rw-r--r--src/nvim/misc1.c16
-rw-r--r--src/nvim/move.c4
-rw-r--r--src/nvim/normal.c3
-rw-r--r--src/nvim/ops.c12
-rw-r--r--src/nvim/option.c70
-rw-r--r--src/nvim/os/process.c6
-rw-r--r--src/nvim/os/stdpaths.c10
-rw-r--r--src/nvim/os/stdpaths_defs.h1
-rw-r--r--src/nvim/path.c9
-rw-r--r--src/nvim/popupmnu.c6
-rw-r--r--src/nvim/quickfix.c1490
-rw-r--r--src/nvim/regexp.c13
-rw-r--r--src/nvim/regexp_defs.h6
-rw-r--r--src/nvim/regexp_nfa.c6
-rw-r--r--src/nvim/screen.c54
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/spell.c15
-rw-r--r--src/nvim/spellfile.c8
-rw-r--r--src/nvim/syntax.c42
-rw-r--r--src/nvim/tag.c205
-rw-r--r--src/nvim/terminal.c25
-rw-r--r--src/nvim/testdir/Makefile9
-rw-r--r--src/nvim/testdir/test14.in94
-rw-r--r--src/nvim/testdir/test14.ok26
-rw-r--r--src/nvim/testdir/test16.in15
-rw-r--r--src/nvim/testdir/test16.ok2
-rw-r--r--src/nvim/testdir/test17.in126
-rw-r--r--src/nvim/testdir/test17.ok33
-rw-r--r--src/nvim/testdir/test17a.in3
-rw-r--r--src/nvim/testdir/test_autocmd.vim296
-rw-r--r--src/nvim/testdir/test_checkpath.vim113
-rw-r--r--src/nvim/testdir/test_debugger.vim232
-rw-r--r--src/nvim/testdir/test_edit.vim16
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim28
-rw-r--r--src/nvim/testdir/test_filetype.vim5
-rw-r--r--src/nvim/testdir/test_findfile.vim1
-rw-r--r--src/nvim/testdir/test_gf.vim35
-rw-r--r--src/nvim/testdir/test_listchars.vim39
-rw-r--r--src/nvim/testdir/test_mapping.vim40
-rw-r--r--src/nvim/testdir/test_normal.vim95
-rw-r--r--src/nvim/testdir/test_quickfix.vim488
-rw-r--r--src/nvim/testdir/test_search.vim24
-rw-r--r--src/nvim/testdir/test_substitute.vim49
-rw-r--r--src/nvim/testdir/test_swap.vim163
-rw-r--r--src/nvim/testdir/test_syntax.vim34
-rw-r--r--src/nvim/testdir/test_tabpage.vim32
-rw-r--r--src/nvim/testdir/test_tagjump.vim101
-rw-r--r--src/nvim/testdir/test_taglist.vim25
-rw-r--r--src/nvim/testdir/test_undo.vim1
-rw-r--r--src/nvim/testdir/test_visual.vim24
-rw-r--r--src/nvim/tui/input.c8
-rw-r--r--src/nvim/tui/input.h3
-rw-r--r--src/nvim/tui/tui.c8
-rw-r--r--src/nvim/ui.c15
-rw-r--r--src/nvim/ui.h6
-rw-r--r--src/nvim/undo.c20
-rw-r--r--src/nvim/vim.h1
-rw-r--r--src/nvim/window.c48
84 files changed, 4665 insertions, 1701 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index e27c85cd4f..9a5ffecad4 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -15,6 +15,7 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/getchar.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
@@ -97,7 +98,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv;
}
-/// Activate updates from this buffer to the current channel.
+/// Activates buffer-update events on the channel.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@@ -106,7 +107,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// `nvim_buf_lines_event`. Otherwise, the first notification will be
/// a `nvim_buf_changedtick_event`
/// @param opts Optional parameters. Reserved for future use.
-/// @param[out] err Details of an error that may have occurred
+/// @param[out] err Error details, if any
/// @return False when updates couldn't be enabled because the buffer isn't
/// loaded or `opts` contained an invalid key; otherwise True.
Boolean nvim_buf_attach(uint64_t channel_id,
@@ -129,12 +130,12 @@ Boolean nvim_buf_attach(uint64_t channel_id,
return buf_updates_register(buf, channel_id, send_buffer);
}
-//
-/// Deactivate updates from this buffer to the current channel.
+
+/// Deactivates buffer-update events on the channel.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param[out] err Details of an error that may have occurred
+/// @param[out] err Error details, if any
/// @return False when updates couldn't be disabled because the buffer
/// isn't loaded; otherwise True.
Boolean nvim_buf_detach(uint64_t channel_id,
@@ -474,6 +475,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
false);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
+ fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
end:
for (size_t i = 0; i < new_len; i++) {
@@ -575,6 +577,31 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
return keymap_array(mode, buf);
}
+/// Sets a buffer-local |mapping| for the given mode.
+///
+/// @see |nvim_set_keymap()|
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(6)
+{
+ modify_keymap(buffer, false, mode, lhs, rhs, opts, err);
+}
+
+/// Unmaps a buffer-local |mapping| for the given mode.
+///
+/// @see |nvim_del_keymap()|
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err)
+ FUNC_API_SINCE(6)
+{
+ String rhs = { .data = "", .size = 0 };
+ Dictionary opts = ARRAY_DICT_INIT;
+ modify_keymap(buffer, true, mode, lhs, rhs, opts, err);
+}
+
/// Gets a map of buffer-local |user-commands|.
///
/// @param buffer Buffer handle, or 0 for current buffer
@@ -1106,6 +1133,26 @@ free_exit:
return 0;
}
+// Check if deleting lines made the cursor position invalid.
+// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
+static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
+{
+ if (curwin->w_cursor.lnum >= lo) {
+ // Adjust cursor position if it's in/after the changed lines.
+ if (curwin->w_cursor.lnum >= hi) {
+ curwin->w_cursor.lnum += extra;
+ check_cursor_col();
+ } else if (extra < 0) {
+ curwin->w_cursor.lnum = lo;
+ check_cursor();
+ } else {
+ check_cursor_col();
+ }
+ changed_cline_bef_curs();
+ }
+ invalidate_botline();
+}
+
// Normalizes 0-based indexes to buffer line numbers
static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
{
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index c2b382804d..521ec11906 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -744,6 +744,232 @@ String ga_take_string(garray_T *ga)
return str;
}
+/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
+/// functions like @ref nvim_buf_set_keymap.
+///
+/// Arguments are handled like @ref nvim_set_keymap unless noted.
+/// @param buffer Buffer handle for a specific buffer, or 0 for the current
+/// buffer, or -1 to signify global behavior ("all buffers")
+/// @param is_unmap When true, removes the mapping that matches {lhs}.
+void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs,
+ String rhs, Dictionary opts, Error *err)
+{
+ char *err_msg = NULL; // the error message to report, if any
+ char *err_arg = NULL; // argument for the error message format string
+ ErrorType err_type = kErrorTypeNone;
+
+ char_u *lhs_buf = NULL;
+ char_u *rhs_buf = NULL;
+
+ bool global = (buffer == -1);
+ if (global) {
+ buffer = 0;
+ }
+ buf_T *target_buf = find_buffer_by_handle(buffer, err);
+
+ MapArguments parsed_args;
+ memset(&parsed_args, 0, sizeof(parsed_args));
+ if (parse_keymap_opts(opts, &parsed_args, err)) {
+ goto fail_and_free;
+ }
+ parsed_args.buffer = !global;
+
+ set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
+ (char_u *)rhs.data, rhs.size,
+ CPO_TO_CPO_FLAGS, &parsed_args);
+
+ if (parsed_args.lhs_len > MAXMAPLEN) {
+ err_msg = "LHS exceeds maximum map length: %s";
+ err_arg = lhs.data;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ if (mode.size > 1) {
+ err_msg = "Shortname is too long: %s";
+ err_arg = mode.data;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+ int mode_val; // integer value of the mapping mode, to be passed to do_map()
+ char_u *p = (char_u *)((mode.size) ? mode.data : "m");
+ if (STRNCMP(p, "!", 2) == 0) {
+ mode_val = get_map_mode(&p, true); // mapmode-ic
+ } else {
+ mode_val = get_map_mode(&p, false);
+ if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
+ && mode.size > 0) {
+ // get_map_mode() treats unrecognized mode shortnames as ":map".
+ // This is an error unless the given shortname was empty string "".
+ err_msg = "Invalid mode shortname: \"%s\"";
+ err_arg = (char *)p;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+ }
+
+ if (parsed_args.lhs_len == 0) {
+ err_msg = "Invalid (empty) LHS";
+ err_arg = "";
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ bool is_noremap = parsed_args.noremap;
+ assert(!(is_unmap && is_noremap));
+
+ if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
+ if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
+ parsed_args.rhs_is_noop = true;
+ } else {
+ // the given RHS was nonempty and not a <Nop>, but was parsed as if it
+ // were empty?
+ assert(false && "Failed to parse nonempty RHS!");
+ err_msg = "Parsing of nonempty RHS failed: %s";
+ err_arg = rhs.data;
+ err_type = kErrorTypeException;
+ goto fail_with_message;
+ }
+ } else if (is_unmap && parsed_args.rhs_len) {
+ err_msg = "Gave nonempty RHS in unmap command: %s";
+ err_arg = (char *)parsed_args.rhs;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ // buf_do_map() reads noremap/unmap as its own argument.
+ int maptype_val = 0;
+ if (is_unmap) {
+ maptype_val = 1;
+ } else if (is_noremap) {
+ maptype_val = 2;
+ }
+
+ switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
+ case 0:
+ break;
+ case 1:
+ api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
+ goto fail_and_free;
+ case 2:
+ api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
+ goto fail_and_free;
+ case 5:
+ api_set_error(err, kErrorTypeException,
+ "E227: mapping already exists for %s", parsed_args.lhs);
+ goto fail_and_free;
+ default:
+ assert(false && "Unrecognized return code!");
+ goto fail_and_free;
+ } // switch
+
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+
+ return;
+
+fail_with_message:
+ api_set_error(err, err_type, err_msg, err_arg);
+
+fail_and_free:
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ return;
+}
+
+/// Read in the given opts, setting corresponding flags in `out`.
+///
+/// @param opts A dictionary passed to @ref nvim_set_keymap or
+/// @ref nvim_buf_set_keymap.
+/// @param[out] out MapArguments object in which to set parsed
+/// |:map-arguments| flags.
+/// @param[out] err Error details, if any.
+///
+/// @returns Zero on success, nonzero on failure.
+Integer parse_keymap_opts(Dictionary opts, MapArguments *out, Error *err)
+{
+ char *err_msg = NULL; // the error message to report, if any
+ char *err_arg = NULL; // argument for the error message format string
+ ErrorType err_type = kErrorTypeNone;
+
+ out->buffer = false;
+ out->nowait = false;
+ out->silent = false;
+ out->script = false;
+ out->expr = false;
+ out->unique = false;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ KeyValuePair *key_and_val = &opts.items[i];
+ char *optname = key_and_val->key.data;
+
+ if (key_and_val->value.type != kObjectTypeBoolean) {
+ err_msg = "Gave non-boolean value for an opt: %s";
+ err_arg = optname;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ bool was_valid_opt = false;
+ switch (optname[0]) {
+ // note: strncmp up to and including the null terminator, so that
+ // "nowaitFoobar" won't match against "nowait"
+
+ // don't recognize 'buffer' as a key; user shouldn't provide <buffer>
+ // when calling nvim_set_keymap or nvim_buf_set_keymap, since it can be
+ // inferred from which function they called
+ case 'n':
+ if (STRNCMP(optname, "noremap", 8) == 0) {
+ was_valid_opt = true;
+ out->noremap = key_and_val->value.data.boolean;
+ } else if (STRNCMP(optname, "nowait", 7) == 0) {
+ was_valid_opt = true;
+ out->nowait = key_and_val->value.data.boolean;
+ }
+ break;
+ case 's':
+ if (STRNCMP(optname, "silent", 7) == 0) {
+ was_valid_opt = true;
+ out->silent = key_and_val->value.data.boolean;
+ } else if (STRNCMP(optname, "script", 7) == 0) {
+ was_valid_opt = true;
+ out->script = key_and_val->value.data.boolean;
+ }
+ break;
+ case 'e':
+ if (STRNCMP(optname, "expr", 5) == 0) {
+ was_valid_opt = true;
+ out->expr = key_and_val->value.data.boolean;
+ }
+ break;
+ case 'u':
+ if (STRNCMP(optname, "unique", 7) == 0) {
+ was_valid_opt = true;
+ out->unique = key_and_val->value.data.boolean;
+ }
+ break;
+ default:
+ break;
+ } // switch
+ if (!was_valid_opt) {
+ err_msg = "Invalid key: %s";
+ err_arg = optname;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+ } // for
+
+ return 0;
+
+fail_with_message:
+ api_set_error(err, err_type, err_msg, err_arg);
+ return 1;
+}
+
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
/// with NUL.
///
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 0634764f13..cc74824402 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -5,6 +5,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/vim.h"
+#include "nvim/getchar.h"
#include "nvim/memory.h"
#include "nvim/ex_eval.h"
#include "nvim/lib/kvec.h"
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index d50a91f261..4f28ea5af3 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -76,6 +76,21 @@ void remote_ui_wait_for_attach(void)
pmap_has(uint64_t)(connected_uis, CHAN_STDIO));
}
+/// Activates UI events on the channel.
+///
+/// Entry point of all UI clients. Allows |\-\-embed| to continue startup.
+/// Implies that the client is ready to show the UI. Adds the client to the
+/// list of UIs. |nvim_list_uis()|
+///
+/// @note If multiple UI clients are attached, the global screen dimensions
+/// degrade to the smallest client. E.g. if client A requests 80x40 but
+/// client B requests 200x100, the global screen has size 80x40.
+///
+/// @param channel_id
+/// @param width Requested screen columns
+/// @param height Requested screen rows
+/// @param options |ui-option| map
+/// @param[out] err Error details, if any
void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
Dictionary options, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
@@ -95,6 +110,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->width = (int)width;
ui->height = (int)height;
ui->rgb = true;
+ ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
ui->grid_clear = remote_ui_grid_clear;
ui->grid_cursor_goto = remote_ui_grid_cursor_goto;
@@ -164,6 +180,12 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height,
api_free_dictionary(opts);
}
+/// Deactivates UI events on the channel.
+///
+/// Removes the client from the list of UIs. |nvim_list_uis()|
+///
+/// @param channel_id
+/// @param[out] err Error details, if any
void nvim_ui_detach(uint64_t channel_id, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
@@ -215,6 +237,15 @@ void nvim_ui_set_option(uint64_t channel_id, String name,
static void ui_set_option(UI *ui, bool init, String name, Object value,
Error *error)
{
+ if (strequal(name.data, "override")) {
+ if (value.type != kObjectTypeBoolean) {
+ api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
+ return;
+ }
+ ui->override = value.data.boolean;
+ return;
+ }
+
if (strequal(name.data, "rgb")) {
if (value.type != kObjectTypeBoolean) {
api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f54ac5072c..b8c863704a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1198,12 +1198,29 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
rpc_unsubscribe(channel_id, e);
}
+/// Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or
+/// "#rrggbb" hexadecimal string.
+///
+/// Example:
+/// <pre>
+/// :echo nvim_get_color_by_name("Pink")
+/// :echo nvim_get_color_by_name("#cbcbcb")
+/// </pre>
+///
+/// @param name Color name or "#rrggbb" string
+/// @return 24-bit RGB value, or -1 for invalid argument.
Integer nvim_get_color_by_name(String name)
FUNC_API_SINCE(1)
{
return name_to_color((char_u *)name.data);
}
+/// Returns a map of color names and RGB values.
+///
+/// Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values
+/// (e.g. 65535).
+///
+/// @return Map of color names and RGB values.
Dictionary nvim_get_color_map(void)
FUNC_API_SINCE(1)
{
@@ -1245,6 +1262,49 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
return keymap_array(mode, NULL);
}
+/// Sets a global |mapping| for the given mode.
+///
+/// To set a buffer-local mapping, use |nvim_buf_set_keymap()|.
+///
+/// Unlike |:map|, leading/trailing whitespace is accepted as part of the {lhs}
+/// or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
+///
+/// Example:
+/// <pre>
+/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+/// </pre>
+///
+/// is equivalent to:
+/// <pre>
+/// nmap <nowait> <Space><NL> <Nop>
+/// </pre>
+///
+/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
+/// or "!" for |:map!|, or empty string for |:map|.
+/// @param lhs Left-hand-side |{lhs}| of the mapping.
+/// @param rhs Right-hand-side |{rhs}| of the mapping.
+/// @param opts Optional parameters map. Accepts all |:map-arguments|
+/// as keys excluding |<buffer>| but including |noremap|.
+/// Values are Booleans. Unknown key is an error.
+/// @param[out] err Error details, if any.
+void nvim_set_keymap(String mode, String lhs, String rhs,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(6)
+{
+ modify_keymap(-1, false, mode, lhs, rhs, opts, err);
+}
+
+/// Unmaps a global |mapping| for the given mode.
+///
+/// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
+///
+/// @see |nvim_set_keymap()|
+void nvim_del_keymap(String mode, String lhs, Error *err)
+ FUNC_API_SINCE(6)
+{
+ nvim_buf_del_keymap(-1, mode, lhs, err);
+}
+
/// Gets a map of global (non-buffer-local) Ex commands.
///
/// Currently only |user-commands| are supported, not builtin Ex commands.
@@ -2031,10 +2091,10 @@ Dictionary nvim__stats(void)
/// Gets a list of dictionaries representing attached UIs.
///
/// @return Array of UI dictionaries, each with these keys:
-/// - "height" requested height of the UI
-/// - "width" requested width of the UI
+/// - "height" Requested height of the UI
+/// - "width" Requested width of the UI
/// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|)
-/// - "ext_..." Requested UI extensions, see |ui-options|
+/// - "ext_..." Requested UI extensions, see |ui-option|
/// - "chan" Channel id of remote UI (not present for TUI)
Array nvim_list_uis(void)
FUNC_API_SINCE(4)
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index d628c517bb..cdb226b94d 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -5112,14 +5112,15 @@ chk_modeline(
*e = NUL; // truncate the set command
if (*s != NUL) { // skip over an empty "::"
+ const int secure_save = secure;
save_SID = current_SID;
current_SID = SID_MODELINE;
// Make sure no risky things are executed as a side effect.
- secure++;
+ secure = 1;
retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags);
- secure--;
+ secure = secure_save;
current_SID = save_SID;
if (retval == FAIL) { // stop if error found
break;
@@ -5211,8 +5212,8 @@ char_u *buf_spname(buf_T *buf)
return (char_u *)_(msg_qflist);
}
}
- /* There is no _file_ when 'buftype' is "nofile", b_sfname
- * contains the name as specified by the user */
+ // There is no _file_ when 'buftype' is "nofile", b_sfname
+ // contains the name as specified by the user.
if (bt_nofile(buf)) {
if (buf->b_sfname != NULL) {
return buf->b_sfname;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 2e6f24d9c4..5e28a7b513 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -388,24 +388,25 @@ typedef struct {
* a window may have its own instance.
*/
typedef struct {
- hashtab_T b_keywtab; /* syntax keywords hash table */
- hashtab_T b_keywtab_ic; /* idem, ignore case */
- int b_syn_error; /* TRUE when error occurred in HL */
- int b_syn_ic; /* ignore case for :syn cmds */
- int b_syn_spell; /* SYNSPL_ values */
- garray_T b_syn_patterns; /* table for syntax patterns */
- garray_T b_syn_clusters; /* table for syntax clusters */
- int b_spell_cluster_id; /* @Spell cluster ID or 0 */
- int b_nospell_cluster_id; /* @NoSpell cluster ID or 0 */
- int b_syn_containedin; /* TRUE when there is an item with a
- "containedin" argument */
- int b_syn_sync_flags; /* flags about how to sync */
- short b_syn_sync_id; /* group to sync on */
- long b_syn_sync_minlines; /* minimal sync lines offset */
- long b_syn_sync_maxlines; /* maximal sync lines offset */
- long b_syn_sync_linebreaks; /* offset for multi-line pattern */
- char_u *b_syn_linecont_pat; /* line continuation pattern */
- regprog_T *b_syn_linecont_prog; /* line continuation program */
+ hashtab_T b_keywtab; // syntax keywords hash table
+ hashtab_T b_keywtab_ic; // idem, ignore case
+ int b_syn_error; // TRUE when error occurred in HL
+ bool b_syn_slow; // true when 'redrawtime' reached
+ int b_syn_ic; // ignore case for :syn cmds
+ int b_syn_spell; // SYNSPL_ values
+ garray_T b_syn_patterns; // table for syntax patterns
+ garray_T b_syn_clusters; // table for syntax clusters
+ int b_spell_cluster_id; // @Spell cluster ID or 0
+ int b_nospell_cluster_id; // @NoSpell cluster ID or 0
+ int b_syn_containedin; // TRUE when there is an item with a
+ // "containedin" argument
+ int b_syn_sync_flags; // flags about how to sync
+ int16_t b_syn_sync_id; // group to sync on
+ long b_syn_sync_minlines; // minimal sync lines offset
+ long b_syn_sync_maxlines; // maximal sync lines offset
+ long b_syn_sync_linebreaks; // offset for multi-line pattern
+ char_u *b_syn_linecont_pat; // line continuation pattern
+ regprog_T *b_syn_linecont_prog; // line continuation program
syn_time_T b_syn_linecont_time;
int b_syn_linecont_ic; /* ignore-case flag for above */
int b_syn_topgrp; /* for ":syntax include" */
@@ -998,6 +999,16 @@ typedef struct {
.relative = 0, .external = false, \
.focusable = true })
+// Structure to store last cursor position and topline. Used by check_lnums()
+// and reset_lnums().
+typedef struct
+{
+ int w_topline_save; // original topline value
+ int w_topline_corr; // corrected topline value
+ pos_T w_cursor_save; // original cursor position
+ pos_T w_cursor_corr; // corrected cursor position
+} pos_save_T;
+
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1090,17 +1101,18 @@ struct window_S {
colnr_T w_skipcol; /* starting column when a single line
doesn't fit in the window */
- /*
- * Layout of the window in the screen.
- * May need to add "msg_scrolled" to "w_winrow" in rare situations.
- */
- int w_winrow; /* first row of window in screen */
- int w_height; /* number of rows in window, excluding
- status/command line(s) */
- int w_status_height; /* number of status lines (0 or 1) */
- int w_wincol; /* Leftmost column of window in screen. */
- int w_width; /* Width of window, excluding separation. */
- int w_vsep_width; /* Number of separator columns (0 or 1). */
+ //
+ // Layout of the window in the screen.
+ // May need to add "msg_scrolled" to "w_winrow" in rare situations.
+ //
+ int w_winrow; // first row of window in screen
+ int w_height; // number of rows in window, excluding
+ // status/command line(s)
+ int w_status_height; // number of status lines (0 or 1)
+ int w_wincol; // Leftmost column of window in screen.
+ int w_width; // Width of window, excluding separation.
+ int w_vsep_width; // Number of separator columns (0 or 1).
+ pos_save_T w_save_cursor; // backup of cursor pos and topline
// inner size of window, which can be overridden by external UI
int w_height_inner;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 9f88f1e393..bd61925b0d 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3935,7 +3935,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
int op;
varnumber_T n1, n2;
bool use_float = false;
- float_T f1 = 0, f2;
+ float_T f1 = 0, f2 = 0;
bool error = false;
/*
@@ -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
@@ -6877,6 +6877,27 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype)
}
}
+static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
+ garray_T ga;
+
+ called_vim_beep = false;
+ suppress_errthrow = true;
+ emsg_silent = false;
+ do_cmdline_cmd(cmd);
+ if (!called_vim_beep) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)"command did not beep: ");
+ ga_concat(&ga, (const char_u *)cmd);
+ assert_error(&ga);
+ ga_clear(&ga);
+ }
+
+ suppress_errthrow = false;
+ emsg_on_display = false;
+}
+
// "assert_equal(expected, actual[, msg])" function
static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -10096,7 +10117,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// match added with matchaddpos()
for (i = 0; i < MAXPOSMATCH; i++) {
llpos_T *llpos;
- char buf[6];
+ char buf[30]; // use 30 to avoid compiler warning
llpos = &cur->pos.pos[i];
if (llpos->lnum == 0) {
@@ -10383,6 +10404,23 @@ static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
getwinvar(argvars, rettv, 1);
}
+// "gettagstack()" function
+static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = curwin; // default is current window
+
+ tv_dict_alloc_ret(rettv);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+ }
+
+ get_tagstack(wp, rettv->vval.v_dict);
+}
+
/// Returns information about a window as a dictionary.
static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
{
@@ -10453,6 +10491,14 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
}
+// "getwinpos({timeout})" function
+static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+}
+
/*
* "getwinposx()" function
*/
@@ -11445,25 +11491,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.
@@ -11471,9 +11513,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++;
@@ -12840,33 +12880,36 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char buf[NUMBUFLEN];
const char *const dir = tv_get_string_buf(&argvars[0], buf);
if (*dir == NUL) {
- rettv->vval.v_number = FAIL;
- } else {
- if (*path_tail((char_u *)dir) == NUL) {
- // Remove trailing slashes.
- *path_tail_with_sep((char_u *)dir) = NUL;
- }
+ return;
+ }
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prot = tv_get_number_chk(&argvars[2], NULL);
- }
- if (prot != -1 && strcmp(tv_get_string(&argvars[1]), "p") == 0) {
- char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir);
- if (ret != 0) {
- EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
- xfree(failed_dir);
- rettv->vval.v_number = FAIL;
- return;
- } else {
- rettv->vval.v_number = OK;
- return;
- }
+ if (*path_tail((char_u *)dir) == NUL) {
+ // Remove trailing slashes.
+ *path_tail_with_sep((char_u *)dir) = NUL;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prot = tv_get_number_chk(&argvars[2], NULL);
+ if (prot == -1) {
+ return;
+ }
+ }
+ if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
+ char *failed_dir;
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir);
+ if (ret != 0) {
+ EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ rettv->vval.v_number = FAIL;
+ return;
+ } else {
+ rettv->vval.v_number = OK;
+ return;
}
}
- rettv->vval.v_number = prot == -1 ? FAIL : vim_mkdir_emsg(dir, prot);
}
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
}
/// "mode()" function
@@ -14849,7 +14892,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
skip_args:
if (!title) {
- title = (wp ? "setloclist()" : "setqflist()");
+ title = (wp ? ":setloclist()" : ":setqflist()");
}
recursive++;
@@ -14925,7 +14968,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// match from matchaddpos()
for (i = 1; i < 9; i++) {
- char buf[5];
+ char buf[30]; // use 30 to avoid compiler warning
snprintf(buf, sizeof(buf), "pos%d", i);
dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
if (pos_di != NULL) {
@@ -15175,6 +15218,60 @@ static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
setwinvar(argvars, rettv, 1);
}
+// "settagstack()" function
+static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ static char *e_invact2 = N_("E962: Invalid action: '%s'");
+ win_T *wp;
+ dict_T *d;
+ int action = 'r';
+
+ rettv->vval.v_number = -1;
+
+ // first argument: window number or id
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+
+ // second argument: dict with items to set in the tag stack
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ d = argvars[1].vval.v_dict;
+ if (d == NULL) {
+ return;
+ }
+
+ // third argument: action - 'a' for append and 'r' for replace.
+ // default is to replace the stack.
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ action = 'r';
+ } else if (argvars[2].v_type == VAR_STRING) {
+ const char *actstr;
+ actstr = tv_get_string_chk(&argvars[2]);
+ if (actstr == NULL) {
+ return;
+ }
+ if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL) {
+ action = *actstr;
+ } else {
+ EMSG2(_(e_invact2), actstr);
+ return;
+ }
+ } else {
+ EMSG(_(e_stringreq));
+ return;
+ }
+
+ if (set_tagstack(wp, d, action) == OK) {
+ rettv->vval.v_number = 0;
+ } else {
+ EMSG(_(e_listreq));
+ }
+}
+
/*
* "setwinvar()" function
*/
@@ -16394,6 +16491,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)
{
@@ -18176,7 +18294,7 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,
* Return FAIL when conversion is not possible, doesn't check the position for
* validity.
*/
-static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
+int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
{
list_T *l;
long i = 0;
@@ -19693,16 +19811,15 @@ void ex_echo(exarg_T *eap)
{
char_u *arg = eap->arg;
typval_T rettv;
- bool needclr = true;
bool atstart = true;
const int did_emsg_before = did_emsg;
if (eap->skip)
++emsg_skip;
while (*arg != NUL && *arg != '|' && *arg != '\n' && !got_int) {
- /* If eval1() causes an error message the text from the command may
- * still need to be cleared. E.g., "echo 22,44". */
- need_clr_eos = needclr;
+ // If eval1() causes an error message the text from the command may
+ // still need to be cleared. E.g., "echo 22,44".
+ need_clr_eos = true;
{
char_u *p = arg;
@@ -19746,14 +19863,14 @@ void ex_echo(exarg_T *eap)
}
eap->nextcmd = check_nextcmd(arg);
- if (eap->skip)
- --emsg_skip;
- else {
- /* remove text that may still be there from the command */
- if (needclr)
- msg_clr_eos();
- if (eap->cmdidx == CMD_echo)
+ if (eap->skip) {
+ emsg_skip--;
+ } else {
+ // remove text that may still be there from the command
+ msg_clr_eos();
+ if (eap->cmdidx == CMD_echo) {
msg_end();
+ }
}
}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 311897b3cd..a7f8461fc3 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -25,6 +25,7 @@ return {
arglistid={args={0, 2}},
argv={args={0, 1}},
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
+ assert_beeps={args={1, 2}},
assert_equal={args={2, 3}},
assert_exception={args={1, 2}},
assert_fails={args={1, 2}},
@@ -141,7 +142,9 @@ return {
gettabinfo={args={0, 1}},
gettabvar={args={2, 3}},
gettabwinvar={args={3, 4}},
+ gettagstack={args={0, 1}},
getwininfo={args={0, 1}},
+ getwinpos={args={0, 1}},
getwinposx={},
getwinposy={},
getwinvar={args={2, 3}},
@@ -267,6 +270,7 @@ return {
setreg={args={2, 3}},
settabvar={args=3},
settabwinvar={args=4},
+ settagstack={args={2, 3}},
setwinvar={args=3},
sha256={args=1},
shellescape={args={1, 2}},
@@ -299,6 +303,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},
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 912aecafec..ffb46abfea 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -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)
@@ -1672,6 +1667,27 @@ int tv_dict_add_str(dict_T *const d,
/// 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
/// creating a new copy.
///
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index 6812b342bf..19907d2e45 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -21,16 +21,14 @@
#endif
void rstream_init_fd(Loop *loop, Stream *stream, int fd, size_t bufsize)
- FUNC_ATTR_NONNULL_ARG(1)
- FUNC_ATTR_NONNULL_ARG(2)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
stream_init(loop, stream, fd, NULL);
rstream_init(stream, bufsize);
}
void rstream_init_stream(Stream *stream, uv_stream_t *uvstream, size_t bufsize)
- FUNC_ATTR_NONNULL_ARG(1)
- FUNC_ATTR_NONNULL_ARG(2)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
stream_init(NULL, stream, -1, uvstream);
rstream_init(stream, bufsize);
@@ -138,6 +136,10 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
}
// Called by the by the 'idle' handle to emulate a reading event
+//
+// Idle callbacks are invoked once per event loop:
+// - to perform some very low priority activity.
+// - to keep the loop "alive" (so there is always an event to process)
static void fread_idle_cb(uv_idle_t *handle)
{
uv_fs_t req;
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index ba25b76ec7..70b1d890d6 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -53,9 +53,19 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
stream->uv.idle.data = stream;
} else {
assert(type == UV_NAMED_PIPE || type == UV_TTY);
+#ifdef WIN32
+ if (type == UV_TTY) {
+ uv_tty_init(&loop->uv, &stream->uv.tty, fd, 0);
+ uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_RAW);
+ stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty);
+ } else {
+#endif
uv_pipe_init(&loop->uv, &stream->uv.pipe, 0);
uv_pipe_open(&stream->uv.pipe, fd);
stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.pipe);
+#ifdef WIN32
+ }
+#endif
}
}
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index 694dab6568..a5c33a66a2 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -36,6 +36,9 @@ struct stream {
uv_pipe_t pipe;
uv_tcp_t tcp;
uv_idle_t idle;
+#ifdef WIN32
+ uv_tty_t tty;
+#endif
} uv;
uv_stream_t *uvstream;
uv_buf_t uvbuf;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 150e13efd5..560f4e5df2 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -855,7 +855,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return FAIL;
for (extra = 0, l = line1; l <= line2; l++) {
str = vim_strsave(ml_get(l + extra));
- ml_append(dest + l - line1, str, (colnr_T)0, FALSE);
+ ml_append(dest + l - line1, str, (colnr_T)0, false);
xfree(str);
if (dest < line1)
extra++;
@@ -914,9 +914,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL)
return FAIL;
- for (l = line1; l <= line2; l++)
- ml_delete(line1 + extra, TRUE);
-
+ for (l = line1; l <= line2; l++) {
+ ml_delete(line1 + extra, true);
+ }
if (!global_busy && num_lines > p_report) {
if (num_lines == 1)
MSG(_("1 line moved"));
@@ -982,7 +982,7 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
/* need to use vim_strsave() because the line will be unlocked within
* ml_append() */
p = vim_strsave(ml_get(line1));
- ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, FALSE);
+ ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, false);
xfree(p);
/* situation 2: skip already copied lines */
@@ -2720,7 +2720,7 @@ static int append_indent = 0; /* autoindent for first line */
void ex_append(exarg_T *eap)
{
char_u *theline;
- int did_undo = FALSE;
+ bool did_undo = false;
linenr_T lnum = eap->line2;
int indent = 0;
char_u *p;
@@ -2808,16 +2808,16 @@ void ex_append(exarg_T *eap)
if (p[0] == NUL)
theline[0] = NUL;
- did_undo = TRUE;
- ml_append(lnum, theline, (colnr_T)0, FALSE);
+ did_undo = true;
+ ml_append(lnum, theline, (colnr_T)0, false);
appended_lines_mark(lnum + (empty ? 1 : 0), 1L);
xfree(theline);
++lnum;
if (empty) {
- ml_delete(2L, FALSE);
- empty = FALSE;
+ ml_delete(2L, false);
+ empty = 0;
}
}
State = NORMAL;
@@ -2862,7 +2862,7 @@ void ex_change(exarg_T *eap)
for (lnum = eap->line2; lnum >= eap->line1; --lnum) {
if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */
break;
- ml_delete(eap->line1, FALSE);
+ ml_delete(eap->line1, false);
}
/* make sure the cursor is not beyond the end of the file now */
@@ -3692,10 +3692,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
i = msg_scroll;
msg_scroll = 0; /* truncate msg when
needed */
- msg_no_more = TRUE;
- /* write message same highlighting as for
- * wait_return */
- smsg_attr(HL_ATTR(HLF_R),
+ msg_no_more = true;
+ msg_ext_set_kind("confirm_sub");
+ smsg_attr(HL_ATTR(HLF_R), // Same highlight as wait_return().
_("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
msg_no_more = FALSE;
msg_scroll = i;
@@ -3989,8 +3988,9 @@ skip:
++lnum;
if (u_savedel(lnum, nmatch_tl) != OK)
break;
- for (i = 0; i < nmatch_tl; ++i)
- ml_delete(lnum, (int)FALSE);
+ for (i = 0; i < nmatch_tl; i++) {
+ ml_delete(lnum, false);
+ }
mark_adjust(lnum, lnum + nmatch_tl - 1,
(long)MAXLNUM, -nmatch_tl, false);
if (subflags.do_ask) {
@@ -5154,10 +5154,11 @@ void fix_help_buffer(void)
}
convert_setup(&vc, NULL, NULL);
- ml_append(lnum, cp, (colnr_T)0, FALSE);
- if (cp != IObuff)
+ ml_append(lnum, cp, (colnr_T)0, false);
+ if (cp != IObuff) {
xfree(cp);
- ++lnum;
+ }
+ lnum++;
}
fclose(fd);
}
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 15b735dc9e..40ff29d4a8 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1298,7 +1298,12 @@ void dialog_changed(buf_T *buf, bool checkall)
{
char_u buff[DIALOG_MSG_SIZE];
int ret;
- exarg_T ea;
+ // Init ea pseudo-structure, this is needed for the check_overwrite()
+ // function.
+ exarg_T ea = {
+ .append = false,
+ .forceit = false,
+ };
dialog_msg(buff, _("Save changes to \"%s\"?"), buf->b_fname);
if (checkall) {
@@ -1307,10 +1312,6 @@ void dialog_changed(buf_T *buf, bool checkall)
ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
}
- // Init ea pseudo-structure, this is needed for the check_overwrite()
- // function.
- ea.append = ea.forceit = false;
-
if (ret == VIM_YES) {
if (buf->b_fname != NULL
&& check_overwrite(&ea,
@@ -3403,13 +3404,18 @@ char_u *getsourceline(int c, void *cookie, int indent)
// Get the next line and concatenate it when it starts with a
// backslash. We always need to read the next line, keep it in
// sp->nextline.
+ // Also check for a comment in between continuation lines: "\ .
sp->nextline = get_one_sourceline(sp);
- if (sp->nextline != NULL && *(p = skipwhite(sp->nextline)) == '\\') {
+ if (sp->nextline != NULL
+ && (*(p = skipwhite(sp->nextline)) == '\\'
+ || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) {
garray_T ga;
ga_init(&ga, (int)sizeof(char_u), 400);
ga_concat(&ga, line);
- ga_concat(&ga, p + 1);
+ if (*p == '\\') {
+ ga_concat(&ga, p + 1);
+ }
for (;; ) {
xfree(sp->nextline);
sp->nextline = get_one_sourceline(sp);
@@ -3417,15 +3423,16 @@ char_u *getsourceline(int c, void *cookie, int indent)
break;
}
p = skipwhite(sp->nextline);
- if (*p != '\\') {
+ if (*p == '\\') {
+ // Adjust the growsize to the current length to speed up
+ // concatenating many lines.
+ if (ga.ga_len > 400) {
+ ga_set_growsize(&ga, (ga.ga_len > 8000) ? 8000 : ga.ga_len);
+ }
+ ga_concat(&ga, p + 1);
+ } else if (p[0] != '"' || p[1] != '\\' || p[2] != ' ') {
break;
}
- // Adjust the growsize to the current length to speed up
- // concatenating many lines.
- if (ga.ga_len > 400) {
- ga_set_growsize(&ga, (ga.ga_len > 8000) ? 8000 : ga.ga_len);
- }
- ga_concat(&ga, p + 1);
}
ga_append(&ga, NUL);
xfree(line);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 9fc047dde9..9c4a3f389a 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -839,9 +839,10 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
sourcing_lnum = current_exception->throw_lnum;
current_exception->throw_name = NULL;
- discard_current_exception(); /* uses IObuff if 'verbose' */
- suppress_errthrow = TRUE;
- force_abort = TRUE;
+ discard_current_exception(); // uses IObuff if 'verbose'
+ suppress_errthrow = true;
+ force_abort = true;
+ msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993
if (messages != NULL) {
do {
@@ -4505,8 +4506,8 @@ static int get_tabpage_arg(exarg_T *eap)
tab_number = 0;
} else {
tab_number = eap->line2;
- if (!unaccept_arg0 && **eap->cmdlinep == '-') {
- --tab_number;
+ if (!unaccept_arg0 && *skipwhite(*eap->cmdlinep) == '-') {
+ tab_number--;
if (tab_number < unaccept_arg0) {
eap->errmsg = e_invarg;
}
@@ -7213,10 +7214,11 @@ static void ex_read(exarg_T *eap)
else
lnum = 1;
if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) {
- ml_delete(lnum, FALSE);
+ ml_delete(lnum, false);
if (curwin->w_cursor.lnum > 1
- && curwin->w_cursor.lnum >= lnum)
- --curwin->w_cursor.lnum;
+ && curwin->w_cursor.lnum >= lnum) {
+ curwin->w_cursor.lnum--;
+ }
deleted_lines_mark(lnum, 1L);
}
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 3de7204fde..b16023b0ec 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -366,7 +366,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
// redraw the statusline for statuslines that display the current mode
// using the mode() function.
- if (KeyTyped && msg_scrolled == 0) {
+ if (!cmd_silent && msg_scrolled == 0) {
curwin->w_redr_status = true;
redraw_statuslines();
}
@@ -1389,6 +1389,7 @@ static int command_line_handle_key(CommandLineState *s)
}
}
}
+ ccline.special_char = NUL;
redrawcmd();
return command_line_changed(s);
@@ -6109,9 +6110,9 @@ static int open_cmdwin(void)
do {
if (++i == hislen)
i = 0;
- if (history[histtype][i].hisstr != NULL)
- ml_append(lnum++, history[histtype][i].hisstr,
- (colnr_T)0, FALSE);
+ if (history[histtype][i].hisstr != NULL) {
+ ml_append(lnum++, history[histtype][i].hisstr, (colnr_T)0, false);
+ }
} while (i != hisidx[histtype]);
}
}
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index e358f0218e..05611fb8ba 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -687,20 +687,24 @@ char_u *vim_findfile(void *search_ctx_arg)
if (!vim_isAbsName(stackp->ffs_fix_path)
&& search_ctx->ffsc_start_dir) {
if (STRLEN(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCPY(file_path, search_ctx->ffsc_start_dir);
if (!add_pathsep((char *)file_path)) {
+ ff_free_stack_element(stackp);
goto fail;
}
}
// append the fix part of the search path
if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCAT(file_path, stackp->ffs_fix_path);
if (!add_pathsep((char *)file_path)) {
+ ff_free_stack_element(stackp);
goto fail;
}
@@ -715,6 +719,7 @@ char_u *vim_findfile(void *search_ctx_arg)
if (*p > 0) {
(*p)--;
if (len + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
file_path[len++] = '*';
@@ -743,6 +748,7 @@ char_u *vim_findfile(void *search_ctx_arg)
while (*rest_of_wildcards
&& !vim_ispathsep(*rest_of_wildcards)) {
if (len + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
file_path[len++] = *rest_of_wildcards++;
@@ -792,10 +798,12 @@ char_u *vim_findfile(void *search_ctx_arg)
// prepare the filename to be checked for existence below
if (STRLEN(stackp->ffs_filearray[i]) + 1
+ STRLEN(search_ctx->ffsc_file_to_search) >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCPY(file_path, stackp->ffs_filearray[i]);
if (!add_pathsep((char *)file_path)) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCAT(file_path, search_ctx->ffsc_file_to_search);
@@ -964,7 +972,6 @@ char_u *vim_findfile(void *search_ctx_arg)
}
fail:
- ff_free_stack_element(stackp);
xfree(file_path);
return NULL;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index b2840c9402..507bf3c032 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -11,6 +11,7 @@
#include <fcntl.h>
#include "nvim/vim.h"
+#include "nvim/api/private/handle.h"
#include "nvim/ascii.h"
#include "nvim/fileio.h"
#include "nvim/buffer.h"
@@ -47,6 +48,7 @@
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/types.h"
#include "nvim/undo.h"
#include "nvim/window.h"
@@ -308,29 +310,30 @@ readfile (
#ifdef UNIX
int swap_mode = -1; /* protection bits for swap file */
#endif
- int fileformat = 0; /* end-of-line format */
- int keep_fileformat = FALSE;
+ int fileformat = 0; // end-of-line format
+ bool keep_fileformat = false;
+ FileInfo file_info;
int file_readonly;
linenr_T skip_count = 0;
linenr_T read_count = 0;
int msg_save = msg_scroll;
linenr_T read_no_eol_lnum = 0; // non-zero lnum when last line of
// last read was missing the eol
- int file_rewind = false;
+ bool file_rewind = false;
int can_retry;
- linenr_T conv_error = 0; /* line nr with conversion error */
- linenr_T illegal_byte = 0; /* line nr with illegal byte */
- int keep_dest_enc = FALSE; /* don't retry when char doesn't fit
- in destination encoding */
+ linenr_T conv_error = 0; // line nr with conversion error
+ linenr_T illegal_byte = 0; // line nr with illegal byte
+ bool keep_dest_enc = false; // don't retry when char doesn't fit
+ // in destination encoding
int bad_char_behavior = BAD_REPLACE;
/* BAD_KEEP, BAD_DROP or character to
* replace with */
char_u *tmpname = NULL; /* name of 'charconvert' output file */
int fio_flags = 0;
- char_u *fenc; /* fileencoding to use */
- int fenc_alloced; /* fenc_next is in allocated memory */
- char_u *fenc_next = NULL; /* next item in 'fencs' or NULL */
- int advance_fenc = FALSE;
+ char_u *fenc; // fileencoding to use
+ bool fenc_alloced; // fenc_next is in allocated memory
+ char_u *fenc_next = NULL; // next item in 'fencs' or NULL
+ bool advance_fenc = false;
long real_size = 0;
# ifdef USE_ICONV
iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */
@@ -481,7 +484,6 @@ readfile (
if (newfile && !read_stdin && !read_buffer && !read_fifo) {
// Remember time of file.
- FileInfo file_info;
if (os_fileinfo((char *)fname, &file_info)) {
buf_store_file_info(curbuf, &file_info);
curbuf->b_mtime_read = curbuf->b_mtime;
@@ -627,13 +629,30 @@ readfile (
// Set swap file protection bits after creating it.
if (swap_mode > 0 && curbuf->b_ml.ml_mfp != NULL
&& curbuf->b_ml.ml_mfp->mf_fname != NULL) {
- (void)os_setperm((const char *)curbuf->b_ml.ml_mfp->mf_fname,
- (long)swap_mode);
+ const char *swap_fname = (const char *)curbuf->b_ml.ml_mfp->mf_fname;
+
+ // If the group-read bit is set but not the world-read bit, then
+ // the group must be equal to the group of the original file. If
+ // we can't make that happen then reset the group-read bit. This
+ // avoids making the swap file readable to more users when the
+ // primary group of the user is too permissive.
+ if ((swap_mode & 044) == 040) {
+ FileInfo swap_info;
+
+ if (os_fileinfo(swap_fname, &swap_info)
+ && file_info.stat.st_gid != swap_info.stat.st_gid
+ && os_fchown(curbuf->b_ml.ml_mfp->mf_fd, -1, file_info.stat.st_gid)
+ == -1) {
+ swap_mode &= 0600;
+ }
+ }
+
+ (void)os_setperm(swap_fname, swap_mode);
}
#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);
@@ -747,11 +766,11 @@ readfile (
*/
if (eap != NULL && eap->force_enc != 0) {
fenc = enc_canonize(eap->cmd + eap->force_enc);
- fenc_alloced = TRUE;
- keep_dest_enc = TRUE;
+ fenc_alloced = true;
+ keep_dest_enc = true;
} else if (curbuf->b_p_bin) {
- fenc = (char_u *)""; /* binary: don't convert */
- fenc_alloced = FALSE;
+ fenc = (char_u *)""; // binary: don't convert
+ fenc_alloced = false;
} else if (curbuf->b_help) {
// Help files are either utf-8 or latin1. Try utf-8 first, if this
// fails it must be latin1.
@@ -762,12 +781,12 @@ readfile (
fenc_alloced = false;
} else if (*p_fencs == NUL) {
- fenc = curbuf->b_p_fenc; /* use format from buffer */
- fenc_alloced = FALSE;
+ fenc = curbuf->b_p_fenc; // use format from buffer
+ fenc_alloced = false;
} else {
fenc_next = p_fencs; /* try items in 'fileencodings' */
fenc = next_fenc(&fenc_next);
- fenc_alloced = TRUE;
+ fenc_alloced = true;
}
/*
@@ -800,10 +819,11 @@ retry:
error = true;
goto failed;
}
- /* Delete the previously read lines. */
- while (lnum > from)
- ml_delete(lnum--, FALSE);
- file_rewind = FALSE;
+ // Delete the previously read lines.
+ while (lnum > from) {
+ ml_delete(lnum--, false);
+ }
+ file_rewind = false;
if (set_options) {
curbuf->b_p_bomb = FALSE;
curbuf->b_start_bomb = FALSE;
@@ -815,9 +835,9 @@ retry:
* When retrying with another "fenc" and the first time "fileformat"
* will be reset.
*/
- if (keep_fileformat)
- keep_fileformat = FALSE;
- else {
+ if (keep_fileformat) {
+ keep_fileformat = false;
+ } else {
if (eap != NULL && eap->force_ff != 0) {
fileformat = get_fileformat_force(curbuf, eap);
try_unix = try_dos = try_mac = FALSE;
@@ -841,7 +861,7 @@ retry:
/*
* Try the next entry in 'fileencodings'.
*/
- advance_fenc = FALSE;
+ advance_fenc = false;
if (eap != NULL && eap->force_enc != 0) {
/* Conversion given with "++cc=" wasn't possible, read
@@ -851,7 +871,7 @@ retry:
if (fenc_alloced)
xfree(fenc);
fenc = (char_u *)"";
- fenc_alloced = FALSE;
+ fenc_alloced = false;
} else {
if (fenc_alloced)
xfree(fenc);
@@ -860,7 +880,7 @@ retry:
fenc_alloced = (fenc_next != NULL);
} else {
fenc = (char_u *)"";
- fenc_alloced = FALSE;
+ fenc_alloced = false;
}
}
if (tmpname != NULL) {
@@ -926,8 +946,8 @@ retry:
if (tmpname == NULL) {
tmpname = readfile_charconvert(fname, fenc, &fd);
if (tmpname == NULL) {
- /* Conversion failed. Try another one. */
- advance_fenc = TRUE;
+ // Conversion failed. Try another one.
+ advance_fenc = true;
if (fd < 0) {
/* Re-opening the original file failed! */
EMSG(_("E202: Conversion made file unreadable!"));
@@ -945,7 +965,7 @@ retry:
) {
/* Conversion wanted but we can't.
* Try the next conversion in 'fileencodings' */
- advance_fenc = TRUE;
+ advance_fenc = true;
goto retry;
}
}
@@ -1180,14 +1200,14 @@ retry:
if (fio_flags == FIO_UCSBOM) {
if (ccname == NULL) {
- /* No BOM detected: retry with next encoding. */
- advance_fenc = TRUE;
+ // No BOM detected: retry with next encoding.
+ advance_fenc = true;
} else {
/* BOM detected: set "fenc" and jump back */
if (fenc_alloced)
xfree(fenc);
fenc = ccname;
- fenc_alloced = FALSE;
+ fenc_alloced = false;
}
/* retry reading without getting new bytes or rewinding */
skip_read = TRUE;
@@ -1512,9 +1532,9 @@ rewind_retry:
did_iconv = TRUE;
else
# endif
- /* use next item from 'fileencodings' */
- advance_fenc = TRUE;
- file_rewind = TRUE;
+ // use next item from 'fileencodings'
+ advance_fenc = true;
+ file_rewind = true;
goto retry;
}
}
@@ -1648,8 +1668,8 @@ rewind_retry:
fileformat = EOL_UNIX;
if (set_options)
set_fileformat(EOL_UNIX, OPT_LOCAL);
- file_rewind = TRUE;
- keep_fileformat = TRUE;
+ file_rewind = true;
+ keep_fileformat = true;
goto retry;
}
ff_error = EOL_DOS;
@@ -1760,8 +1780,8 @@ failed:
if (!recoverymode) {
/* need to delete the last line, which comes from the empty buffer */
if (newfile && wasempty && !(curbuf->b_ml.ml_flags & ML_EMPTY)) {
- ml_delete(curbuf->b_ml.ml_line_count, FALSE);
- --linecnt;
+ ml_delete(curbuf->b_ml.ml_line_count, false);
+ linecnt--;
}
linecnt = curbuf->b_ml.ml_line_count - linecnt;
if (filesize == 0)
@@ -4894,9 +4914,9 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
/* Copy the lines in "frombuf" to "tobuf". */
curbuf = tobuf;
- for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; ++lnum) {
- p = vim_strsave(ml_get_buf(frombuf, lnum, FALSE));
- if (ml_append(lnum - 1, p, 0, FALSE) == FAIL) {
+ for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; lnum++) {
+ p = vim_strsave(ml_get_buf(frombuf, lnum, false));
+ if (ml_append(lnum - 1, p, 0, false) == FAIL) {
xfree(p);
retval = FAIL;
break;
@@ -4907,13 +4927,14 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
/* Delete all the lines in "frombuf". */
if (retval != FAIL) {
curbuf = frombuf;
- for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; --lnum)
- if (ml_delete(lnum, FALSE) == FAIL) {
- /* Oops! We could try putting back the saved lines, but that
- * might fail again... */
+ for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; lnum--) {
+ if (ml_delete(lnum, false) == FAIL) {
+ // Oops! We could try putting back the saved lines, but that
+ // might fail again...
retval = FAIL;
break;
}
+ }
}
curbuf = tbuf;
@@ -6501,21 +6522,17 @@ bool check_nomodeline(char_u **argp)
return true;
}
-/*
- * Prepare for executing autocommands for (hidden) buffer "buf".
- * Search for a visible window containing the current buffer. If there isn't
- * one then use "aucmd_win".
- * Set "curbuf" and "curwin" to match "buf".
- */
-void
-aucmd_prepbuf (
- aco_save_T *aco, /* structure to save values in */
- buf_T *buf /* new curbuf */
-)
+/// Prepare for executing autocommands for (hidden) buffer `buf`.
+/// If the current buffer is not in any visible window, put it in a temporary
+/// floating window `aucmd_win`.
+/// Set `curbuf` and `curwin` to match `buf`.
+///
+/// @param aco structure to save values in
+/// @param buf new curbuf
+void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
{
- win_T *win;
- int save_ea;
- int save_acd;
+ win_T *win;
+ bool need_append = true; // Append `aucmd_win` to the window list.
/* Find a window that is for the new buffer */
if (buf == curbuf) { /* be quick when buf is curbuf */
@@ -6530,9 +6547,10 @@ aucmd_prepbuf (
}
}
- /* Allocate "aucmd_win" when needed. */
+ // Allocate the `aucmd_win` dummy floating window.
if (win == NULL && aucmd_win == NULL) {
win_alloc_aucmd_win();
+ need_append = false;
}
if (win == NULL && aucmd_win_used)
/* Strange recursive autocommand, fall back to using the current
@@ -6567,21 +6585,16 @@ aucmd_prepbuf (
aco->globaldir = globaldir;
globaldir = NULL;
-
- /* Split the current window, put the aucmd_win in the upper half.
- * We don't want the BufEnter or WinEnter autocommands. */
- block_autocmds();
- make_snapshot(SNAP_AUCMD_IDX);
- save_ea = p_ea;
- p_ea = false;
-
- /* Prevent chdir() call in win_enter_ext(), through do_autochdir(). */
- save_acd = p_acd;
+ block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
+ if (need_append) {
+ win_append(lastwin, aucmd_win);
+ handle_register_window(aucmd_win);
+ win_config_float(aucmd_win, aucmd_win->w_float_config);
+ }
+ // Prevent chdir() call in win_enter_ext(), through do_autochdir()
+ int save_acd = p_acd;
p_acd = false;
-
- (void)win_split_ins(0, WSP_TOP, aucmd_win, 0);
- (void)win_comp_pos(); /* recompute window positions */
- p_ea = save_ea;
+ win_enter(aucmd_win, false);
p_acd = save_acd;
unblock_autocmds();
curwin = aucmd_win;
@@ -6597,8 +6610,6 @@ aucmd_prepbuf (
/// @param aco structure holding saved values
void aucmd_restbuf(aco_save_T *aco)
{
- int dummy;
-
if (aco->use_aucmd_win) {
curbuf->b_nwindows--;
// Find "aucmd_win", it can't be closed, but it may be in another tab page.
@@ -6617,9 +6628,14 @@ void aucmd_restbuf(aco_save_T *aco)
}
win_found:
- // Remove the window and frame from the tree of frames.
- (void)winframe_remove(curwin, &dummy, NULL);
win_remove(curwin, NULL);
+ handle_unregister_window(curwin);
+ if (curwin->w_grid.chars != NULL) {
+ ui_comp_remove_grid(&curwin->w_grid);
+ ui_call_win_hide(curwin->w_grid.handle);
+ grid_free(&curwin->w_grid);
+ }
+
aucmd_win_used = false;
last_status(false); // may need to remove last status line
@@ -6628,8 +6644,6 @@ win_found:
close_tabpage(curtab);
}
- restore_snapshot(SNAP_AUCMD_IDX, false);
- (void)win_comp_pos(); // recompute window positions
unblock_autocmds();
if (win_valid(aco->save_curwin)) {
@@ -7069,6 +7083,8 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
do_cmdline(NULL, getnextac, (void *)&patcmd,
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ reset_lnums(); // restore cursor and topline, unless they were changed
+
if (eap != NULL) {
(void)set_cmdarg(NULL, save_cmdarg);
set_vim_var_nr(VV_CMDBANG, save_cmdbang);
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 0020b57482..f8c7dc613b 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2502,286 +2502,334 @@ int fix_input_buffer(char_u *buf, int len)
return len;
}
-/*
- * map[!] : show all key mappings
- * map[!] {lhs} : show key mapping for {lhs}
- * map[!] {lhs} {rhs} : set key mapping for {lhs} to {rhs}
- * noremap[!] {lhs} {rhs} : same, but no remapping for {rhs}
- * unmap[!] {lhs} : remove key mapping for {lhs}
- * abbr : show all abbreviations
- * abbr {lhs} : show abbreviations for {lhs}
- * abbr {lhs} {rhs} : set abbreviation for {lhs} to {rhs}
- * noreabbr {lhs} {rhs} : same, but no remapping for {rhs}
- * unabbr {lhs} : remove abbreviation for {lhs}
- *
- * maptype: 0 for :map, 1 for :unmap, 2 for noremap.
- *
- * arg is pointer to any arguments. Note: arg cannot be a read-only string,
- * it will be modified.
- *
- * for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING
- * for :map! mode is INSERT + CMDLINE
- * for :cmap mode is CMDLINE
- * for :imap mode is INSERT
- * for :lmap mode is LANGMAP
- * for :nmap mode is NORMAL
- * for :vmap mode is VISUAL + SELECTMODE
- * for :xmap mode is VISUAL
- * for :smap mode is SELECTMODE
- * for :omap mode is OP_PENDING
- * for :tmap mode is TERM_FOCUS
- *
- * for :abbr mode is INSERT + CMDLINE
- * for :iabbr mode is INSERT
- * for :cabbr mode is CMDLINE
- *
- * Return 0 for success
- * 1 for invalid arguments
- * 2 for no match
- * 4 for out of mem (deprecated, WON'T HAPPEN)
- * 5 for entry not unique
- */
-int
-do_map (
- int maptype,
- char_u *arg,
- int mode,
- int abbrev /* not a mapping but an abbreviation */
-)
+/// Replace termcodes in the given LHS and RHS and store the results into the
+/// `lhs` and `rhs` of the given @ref MapArguments struct.
+///
+/// `rhs` and `orig_rhs` will both point to new allocated buffers. `orig_rhs`
+/// will hold a copy of the given `orig_rhs`.
+///
+/// The `*_len` variables will be set appropriately. If the length of
+/// the final `lhs` exceeds `MAXMAPLEN`, `lhs_len` will be set equal to the
+/// original larger length and `lhs` will be truncated.
+///
+/// If RHS is equal to "<Nop>", `rhs` will be the empty string, `rhs_len`
+/// will be zero, and `rhs_is_noop` will be set to true.
+///
+/// Any memory allocated by @ref replace_termcodes is freed before this function
+/// returns.
+///
+/// @param[in] orig_lhs Original mapping LHS, with characters to replace.
+/// @param[in] orig_lhs_len `strlen` of orig_lhs.
+/// @param[in] orig_rhs Original mapping RHS, with characters to replace.
+/// @param[in] orig_rhs_len `strlen` of orig_rhs.
+/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
+/// @param[out] mapargs MapArguments struct holding the replaced strings.
+void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
+ const char_u *orig_rhs, const size_t orig_rhs_len,
+ int cpo_flags, MapArguments *mapargs)
{
- char_u *keys;
- mapblock_T *mp, **mpp;
- char_u *rhs;
- char_u *p;
- int n;
- int len = 0; /* init for GCC */
- int hasarg;
- int haskey;
- int did_it = FALSE;
- int did_local = FALSE;
- int round;
- char_u *keys_buf = NULL;
- char_u *arg_buf = NULL;
- int retval = 0;
- int do_backslash;
- int hash;
- int new_hash;
- mapblock_T **abbr_table;
- mapblock_T **map_table;
- bool unique = false;
- bool nowait = false;
- bool silent = false;
- bool expr = false;
- int noremap;
- char_u *orig_rhs;
+ char_u *lhs_buf = NULL;
+ char_u *rhs_buf = NULL;
- keys = arg;
- map_table = maphash;
- abbr_table = &first_abbr;
+ // If mapping has been given as ^V<C_UP> say, then replace the term codes
+ // with the appropriate two bytes. If it is a shifted special key, unshift
+ // it too, giving another two bytes.
+ //
+ // replace_termcodes() may move the result to allocated memory, which
+ // needs to be freed later (*lhs_buf and *rhs_buf).
+ // replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
+ char_u *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf,
+ true, true, true, cpo_flags);
+ mapargs->lhs_len = STRLEN(replaced);
+ xstrlcpy((char *)mapargs->lhs, (char *)replaced, sizeof(mapargs->lhs));
+
+ mapargs->orig_rhs_len = orig_rhs_len;
+ mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)mapargs->orig_rhs, (char *)orig_rhs,
+ mapargs->orig_rhs_len + 1);
+
+ if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
+ mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
+ mapargs->rhs_len = 0;
+ mapargs->rhs_is_noop = true;
+ } else {
+ replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
+ false, true, true, cpo_flags);
+ mapargs->rhs_len = STRLEN(replaced);
+ mapargs->rhs_is_noop = false;
+ mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)mapargs->rhs, (char *)replaced, mapargs->rhs_len + 1);
+ }
- /* For ":noremap" don't remap, otherwise do remap. */
- if (maptype == 2)
- noremap = REMAP_NONE;
- else
- noremap = REMAP_YES;
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+}
- /* Accept <buffer>, <nowait>, <silent>, <expr> <script> and <unique> in
- * any order. */
- for (;; ) {
- /*
- * Check for "<buffer>": mapping local to buffer.
- */
- if (STRNCMP(keys, "<buffer>", 8) == 0) {
- keys = skipwhite(keys + 8);
- map_table = curbuf->b_maphash;
- abbr_table = &curbuf->b_first_abbr;
+/// Parse a string of |:map-arguments| into a @ref MapArguments struct.
+///
+/// Termcodes, backslashes, CTRL-V's, etc. inside the extracted {lhs} and
+/// {rhs} are replaced by @ref set_maparg_lhs_rhs.
+///
+/// rhs and orig_rhs in the returned mapargs will be set to null or a pointer
+/// to allocated memory and should be freed even on error.
+///
+/// @param[in] strargs String of map args, e.g. "<buffer> <expr><silent>".
+/// May contain leading or trailing whitespace.
+/// @param[in] is_unmap True, if strargs should be parsed like an |:unmap|
+/// command. |:unmap| commands interpret *all* text to the
+/// right of the last map argument as the {lhs} of the
+/// mapping, i.e. a literal ' ' character is treated like
+/// a "<space>", rather than separating the {lhs} from the
+/// {rhs}.
+/// @param[out] mapargs MapArguments struct holding all extracted argument
+/// values.
+/// @return 0 on success, 1 if invalid arguments are detected.
+int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
+{
+ const char_u *to_parse = strargs;
+ to_parse = skipwhite(to_parse);
+ MapArguments parsed_args; // copy these into mapargs "all at once" when done
+ memset(&parsed_args, 0, sizeof(parsed_args));
+
+ // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in
+ // any order.
+ while (true) {
+ if (STRNCMP(to_parse, "<buffer>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.buffer = true;
continue;
}
- /*
- * Check for "<nowait>": don't wait for more characters.
- */
- if (STRNCMP(keys, "<nowait>", 8) == 0) {
- keys = skipwhite(keys + 8);
- nowait = true;
+ if (STRNCMP(to_parse, "<nowait>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.nowait = true;
continue;
}
- /*
- * Check for "<silent>": don't echo commands.
- */
- if (STRNCMP(keys, "<silent>", 8) == 0) {
- keys = skipwhite(keys + 8);
- silent = true;
+ if (STRNCMP(to_parse, "<silent>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.silent = true;
continue;
}
// Ignore obsolete "<special>" modifier.
- if (STRNCMP(keys, "<special>", 9) == 0) {
- keys = skipwhite(keys + 9);
+ if (STRNCMP(to_parse, "<special>", 9) == 0) {
+ to_parse = skipwhite(to_parse + 9);
continue;
}
- /*
- * Check for "<script>": remap script-local mappings only
- */
- if (STRNCMP(keys, "<script>", 8) == 0) {
- keys = skipwhite(keys + 8);
- noremap = REMAP_SCRIPT;
+ if (STRNCMP(to_parse, "<script>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.script = true;
continue;
}
- /*
- * Check for "<expr>": {rhs} is an expression.
- */
- if (STRNCMP(keys, "<expr>", 6) == 0) {
- keys = skipwhite(keys + 6);
- expr = true;
+ if (STRNCMP(to_parse, "<expr>", 6) == 0) {
+ to_parse = skipwhite(to_parse + 6);
+ parsed_args.expr = true;
continue;
}
- /*
- * Check for "<unique>": don't overwrite an existing mapping.
- */
- if (STRNCMP(keys, "<unique>", 8) == 0) {
- keys = skipwhite(keys + 8);
- unique = true;
+
+ if (STRNCMP(to_parse, "<unique>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.unique = true;
continue;
}
break;
}
- validate_maphash();
-
- /*
- * Find end of keys and skip CTRL-Vs (and backslashes) in it.
- * Accept backslash like CTRL-V when 'cpoptions' does not contain 'B'.
- * with :unmap white space is included in the keys, no argument possible.
- */
- p = keys;
- do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
- while (*p && (maptype == 1 || !ascii_iswhite(*p))) {
- if ((p[0] == Ctrl_V || (do_backslash && p[0] == '\\')) && p[1] != NUL) {
- p++; // skip CTRL-V or backslash
+ // Find the next whitespace character, call that the end of {lhs}.
+ //
+ // If a character (e.g. whitespace) is immediately preceded by a CTRL-V,
+ // "scan past" that character, i.e. don't "terminate" LHS with that character
+ // if it's whitespace.
+ //
+ // Treat backslash like CTRL-V when 'cpoptions' does not contain 'B'.
+ //
+ // With :unmap, literal white space is included in the {lhs}; there is no
+ // separate {rhs}.
+ const char_u *lhs_end = to_parse;
+ bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
+ while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) {
+ if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\'))
+ && lhs_end[1] != NUL) {
+ lhs_end++; // skip CTRL-V or backslash
}
- p++;
+ lhs_end++;
+ }
+
+ // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
+ // Use that to initialize {rhs_start}.
+ const char_u *rhs_start = skipwhite(lhs_end);
+
+ // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
+ // (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
+ size_t orig_lhs_len = (size_t)(lhs_end - to_parse);
+ char_u *lhs_to_replace = xcalloc(orig_lhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)lhs_to_replace, (char *)to_parse, orig_lhs_len + 1);
+
+ size_t orig_rhs_len = STRLEN(rhs_start);
+ set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len,
+ rhs_start, orig_rhs_len,
+ CPO_TO_CPO_FLAGS, &parsed_args);
+
+ xfree(lhs_to_replace);
+
+ *mapargs = parsed_args;
+
+ if (parsed_args.lhs_len > MAXMAPLEN) {
+ return 1;
+ }
+ return 0;
+}
+
+/// Sets or removes a mapping or abbreviation in buffer `buf`.
+///
+/// @param maptype @see do_map
+/// @param args Fully parsed and "preprocessed" arguments for the
+/// (un)map/abbrev command. Termcodes should have already been
+/// replaced; whitespace, `<` and `>` signs, etc. in {lhs} and
+/// {rhs} are assumed to be literal components of the mapping.
+/// @param mode @see do_map
+/// @param is_abbrev @see do_map
+/// @param buf Target Buffer
+int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
+ buf_T *buf)
+{
+ mapblock_T *mp, **mpp;
+ char_u *p;
+ int n;
+ int len = 0; // init for GCC
+ int did_it = false;
+ int did_local = false;
+ int round;
+ int retval = 0;
+ int hash;
+ int new_hash;
+ mapblock_T **abbr_table;
+ mapblock_T **map_table;
+ int noremap;
+
+ map_table = maphash;
+ abbr_table = &first_abbr;
+
+ // For ":noremap" don't remap, otherwise do remap.
+ if (maptype == 2) {
+ noremap = REMAP_NONE;
+ } else {
+ noremap = REMAP_YES;
+ }
+
+ if (args->buffer) {
+ // If <buffer> was given, we'll be searching through the buffer's
+ // mappings/abbreviations, not the globals.
+ map_table = buf->b_maphash;
+ abbr_table = &buf->b_first_abbr;
}
- if (*p != NUL) {
- *p++ = NUL;
+ if (args->script) {
+ noremap = REMAP_SCRIPT;
}
- p = skipwhite(p);
- rhs = p;
- hasarg = (*rhs != NUL);
- haskey = (*keys != NUL);
+ validate_maphash();
+
+ bool has_lhs = (args->lhs[0] != NUL);
+ bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop;
- /* check for :unmap without argument */
- if (maptype == 1 && !haskey) {
+ // check for :unmap without argument
+ if (maptype == 1 && !has_lhs) {
retval = 1;
goto theend;
}
- // If mapping has been given as ^V<C_UP> say, then replace the term codes
- // with the appropriate two bytes. If it is a shifted special key, unshift
- // it too, giving another two bytes.
- // replace_termcodes() may move the result to allocated memory, which
- // needs to be freed later (*keys_buf and *arg_buf).
- // replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
- if (haskey) {
- keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
- CPO_TO_CPO_FLAGS);
- }
- orig_rhs = rhs;
- if (hasarg) {
- if (STRICMP(rhs, "<nop>") == 0) { // "<Nop>" means nothing
- rhs = (char_u *)"";
- } else {
- rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, false, true, true,
- CPO_TO_CPO_FLAGS);
- }
- }
+ char_u *lhs = (char_u *)&args->lhs;
+ char_u *rhs = (char_u *)args->rhs;
+ char_u *orig_rhs = args->orig_rhs;
- //
// check arguments and translate function keys
- //
- if (haskey) {
- len = (int)STRLEN(keys);
- if (len > MAXMAPLEN) { /* maximum length of MAXMAPLEN chars */
+ if (has_lhs) {
+ len = (int)args->lhs_len;
+ if (len > MAXMAPLEN) {
retval = 1;
goto theend;
}
- if (abbrev && maptype != 1) {
- /*
- * If an abbreviation ends in a keyword character, the
- * rest must be all keyword-char or all non-keyword-char.
- * Otherwise we won't be able to find the start of it in a
- * vi-compatible way.
- */
+ if (is_abbrev && maptype != 1) {
+ //
+ // If an abbreviation ends in a keyword character, the
+ // rest must be all keyword-char or all non-keyword-char.
+ // Otherwise we won't be able to find the start of it in a
+ // vi-compatible way.
+ //
if (has_mbyte) {
int first, last;
int same = -1;
- first = vim_iswordp(keys);
+ first = vim_iswordp(lhs);
last = first;
- p = keys + (*mb_ptr2len)(keys);
+ p = lhs + (*mb_ptr2len)(lhs);
n = 1;
- while (p < keys + len) {
- ++n; /* nr of (multi-byte) chars */
- last = vim_iswordp(p); /* type of last char */
- if (same == -1 && last != first)
- same = n - 1; /* count of same char type */
+ while (p < lhs + len) {
+ n++; // nr of (multi-byte) chars
+ last = vim_iswordp(p); // type of last char
+ if (same == -1 && last != first) {
+ same = n - 1; // count of same char type
+ }
p += (*mb_ptr2len)(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
retval = 1;
goto theend;
}
- } else if (vim_iswordc(keys[len - 1])) /* ends in keyword char */
- for (n = 0; n < len - 2; ++n)
- if (vim_iswordc(keys[n]) != vim_iswordc(keys[len - 2])) {
+ } else if (vim_iswordc(lhs[len - 1])) { // ends in keyword char
+ for (n = 0; n < len - 2; n++) {
+ if (vim_iswordc(lhs[n]) != vim_iswordc(lhs[len - 2])) {
retval = 1;
goto theend;
}
- /* An abbreviation cannot contain white space. */
- for (n = 0; n < len; ++n)
- if (ascii_iswhite(keys[n])) {
+ } // for
+ }
+ // An abbreviation cannot contain white space.
+ for (n = 0; n < len; n++) {
+ if (ascii_iswhite(lhs[n])) {
retval = 1;
goto theend;
}
+ } // for
}
}
- if (haskey && hasarg && abbrev) /* if we will add an abbreviation */
- no_abbr = FALSE; /* reset flag that indicates there are
- no abbreviations */
+ if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
+ no_abbr = false; // reset flag that indicates there are no abbreviations
+ }
- if (!haskey || (maptype != 1 && !hasarg))
+ if (!has_lhs || (maptype != 1 && !has_rhs)) {
msg_start();
+ }
- /*
- * Check if a new local mapping wasn't already defined globally.
- */
- if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) {
- /* need to loop over all global hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash != 0) /* there is only one abbreviation list */
+ // Check if a new local mapping wasn't already defined globally.
+ if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
+ // need to loop over all global hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
break;
+ }
mp = first_abbr;
- } else
+ } else {
mp = maphash[hash];
+ }
for (; mp != NULL && !got_int; mp = mp->m_next) {
- /* check entries with the same mode */
+ // check entries with the same mode
if ((mp->m_mode & mode) != 0
&& mp->m_keylen == len
- && unique
- && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) {
- if (abbrev)
+ && args->unique
+ && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
+ if (is_abbrev) {
EMSG2(_("E224: global abbreviation already exists for %s"),
- mp->m_keys);
- else
- EMSG2(_("E225: global mapping already exists for %s"),
- mp->m_keys);
+ mp->m_keys);
+ } else {
+ EMSG2(_("E225: global mapping already exists for %s"), mp->m_keys);
+ }
retval = 5;
goto theend;
}
@@ -2789,30 +2837,29 @@ do_map (
}
}
- /*
- * When listing global mappings, also list buffer-local ones here.
- */
- if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) {
- /* need to loop over all global hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash != 0) /* there is only one abbreviation list */
+ // When listing global mappings, also list buffer-local ones here.
+ if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
+ // need to loop over all global hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
break;
- mp = curbuf->b_first_abbr;
- } else
- mp = curbuf->b_maphash[hash];
+ }
+ mp = buf->b_first_abbr;
+ } else {
+ mp = buf->b_maphash[hash];
+ }
for (; mp != NULL && !got_int; mp = mp->m_next) {
- /* check entries with the same mode */
+ // check entries with the same mode
if ((mp->m_mode & mode) != 0) {
- if (!haskey) { /* show all entries */
- showmap(mp, TRUE);
- did_local = TRUE;
+ if (!has_lhs) { // show all entries
+ showmap(mp, true);
+ did_local = true;
} else {
n = mp->m_keylen;
- if (STRNCMP(mp->m_keys, keys,
- (size_t)(n < len ? n : len)) == 0) {
- showmap(mp, TRUE);
- did_local = TRUE;
+ if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
+ showmap(mp, true);
+ did_local = true;
}
}
}
@@ -2820,103 +2867,98 @@ do_map (
}
}
- /*
- * Find an entry in the maphash[] list that matches.
- * For :unmap we may loop two times: once to try to unmap an entry with a
- * matching 'from' part, a second time, if the first fails, to unmap an
- * entry with a matching 'to' part. This was done to allow ":ab foo bar"
- * to be unmapped by typing ":unab foo", where "foo" will be replaced by
- * "bar" because of the abbreviation.
- */
+ // Find an entry in the maphash[] list that matches.
+ // For :unmap we may loop two times: once to try to unmap an entry with a
+ // matching 'from' part, a second time, if the first fails, to unmap an
+ // entry with a matching 'to' part. This was done to allow ":ab foo bar"
+ // to be unmapped by typing ":unab foo", where "foo" will be replaced by
+ // "bar" because of the abbreviation.
for (round = 0; (round == 0 || maptype == 1) && round <= 1
- && !did_it && !got_int; ++round) {
- /* need to loop over all hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash > 0) /* there is only one abbreviation list */
+ && !did_it && !got_int; round++) {
+ // need to loop over all hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash > 0) { // there is only one abbreviation list
break;
+ }
mpp = abbr_table;
- } else
+ } else {
mpp = &(map_table[hash]);
+ }
for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
-
- if (!(mp->m_mode & mode)) { /* skip entries with wrong mode */
+ if (!(mp->m_mode & mode)) { // skip entries with wrong mode
mpp = &(mp->m_next);
continue;
}
- if (!haskey) { /* show all entries */
+ if (!has_lhs) { // show all entries
showmap(mp, map_table != maphash);
- did_it = TRUE;
- } else { /* do we have a match? */
- if (round) { /* second round: Try unmap "rhs" string */
+ did_it = true;
+ } else { // do we have a match?
+ if (round) { // second round: Try unmap "rhs" string
n = (int)STRLEN(mp->m_str);
p = mp->m_str;
} else {
n = mp->m_keylen;
p = mp->m_keys;
}
- if (STRNCMP(p, keys, (size_t)(n < len ? n : len)) == 0) {
- if (maptype == 1) { /* delete entry */
- /* Only accept a full match. For abbreviations we
- * ignore trailing space when matching with the
- * "lhs", since an abbreviation can't have
- * trailing space. */
- if (n != len && (!abbrev || round || n > len
- || *skipwhite(keys + n) != NUL)) {
+ if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) {
+ if (maptype == 1) { // delete entry
+ // Only accept a full match. For abbreviations we
+ // ignore trailing space when matching with the
+ // "lhs", since an abbreviation can't have
+ // trailing space.
+ if (n != len && (!is_abbrev || round || n > len
+ || *skipwhite(lhs + n) != NUL)) {
mpp = &(mp->m_next);
continue;
}
- /*
- * We reset the indicated mode bits. If nothing is
- * left the entry is deleted below.
- */
+ // We reset the indicated mode bits. If nothing is
+ // left the entry is deleted below.
mp->m_mode &= ~mode;
- did_it = TRUE; /* remember we did something */
- } else if (!hasarg) { /* show matching entry */
+ did_it = true; // remember we did something
+ } else if (!has_rhs) { // show matching entry
showmap(mp, map_table != maphash);
- did_it = TRUE;
- } else if (n != len) { /* new entry is ambiguous */
+ did_it = true;
+ } else if (n != len) { // new entry is ambiguous
mpp = &(mp->m_next);
continue;
- } else if (unique) {
- if (abbrev)
- EMSG2(_("E226: abbreviation already exists for %s"),
- p);
- else
+ } else if (args->unique) {
+ if (is_abbrev) {
+ EMSG2(_("E226: abbreviation already exists for %s"), p);
+ } else {
EMSG2(_("E227: mapping already exists for %s"), p);
+ }
retval = 5;
goto theend;
- } else { /* new rhs for existing entry */
- mp->m_mode &= ~mode; /* remove mode bits */
- if (mp->m_mode == 0 && !did_it) { /* reuse entry */
+ } else { // new rhs for existing entry
+ mp->m_mode &= ~mode; // remove mode bits
+ if (mp->m_mode == 0 && !did_it) { // reuse entry
xfree(mp->m_str);
mp->m_str = vim_strsave(rhs);
xfree(mp->m_orig_str);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_noremap = noremap;
- mp->m_nowait = nowait;
- mp->m_silent = silent;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
mp->m_mode = mode;
- mp->m_expr = expr;
+ mp->m_expr = args->expr;
mp->m_script_ID = current_SID;
- did_it = TRUE;
+ did_it = true;
}
}
- if (mp->m_mode == 0) { // entry can be deleted
+ if (mp->m_mode == 0) { // entry can be deleted
mapblock_free(mpp);
- continue; // continue with *mpp
+ continue; // continue with *mpp
}
- /*
- * May need to put this entry into another hash list.
- */
+ // May need to put this entry into another hash list.
new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]);
- if (!abbrev && new_hash != hash) {
+ if (!is_abbrev && new_hash != hash) {
*mpp = mp->m_next;
mp->m_next = map_table[new_hash];
map_table[new_hash] = mp;
- continue; /* continue with *mpp */
+ continue; // continue with *mpp
}
}
}
@@ -2925,13 +2967,13 @@ do_map (
}
}
- if (maptype == 1) { /* delete entry */
+ if (maptype == 1) { // delete entry
if (!did_it) {
- retval = 2; /* no match */
- } else if (*keys == Ctrl_C) {
+ retval = 2; // no match
+ } else if (*lhs == Ctrl_C) {
// If CTRL-C has been unmapped, reuse it for Interrupting.
- if (map_table == curbuf->b_maphash) {
- curbuf->b_mapped_ctrl_c &= ~mode;
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c &= ~mode;
} else {
mapped_ctrl_c &= ~mode;
}
@@ -2939,48 +2981,46 @@ do_map (
goto theend;
}
- if (!haskey || !hasarg) { /* print entries */
- if (!did_it
- && !did_local
- ) {
- if (abbrev)
+ if (!has_lhs || !has_rhs) { // print entries
+ if (!did_it && !did_local) {
+ if (is_abbrev) {
MSG(_("No abbreviation found"));
- else
+ } else {
MSG(_("No mapping found"));
+ }
}
- goto theend; /* listing finished */
+ goto theend; // listing finished
}
- if (did_it) /* have added the new entry already */
+ if (did_it) { // have added the new entry already
goto theend;
+ }
- /*
- * Get here when adding a new entry to the maphash[] list or abbrlist.
- */
+ // Get here when adding a new entry to the maphash[] list or abbrlist.
mp = xmalloc(sizeof(mapblock_T));
// If CTRL-C has been mapped, don't always use it for Interrupting.
- if (*keys == Ctrl_C) {
- if (map_table == curbuf->b_maphash) {
- curbuf->b_mapped_ctrl_c |= mode;
+ if (*lhs == Ctrl_C) {
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c |= mode;
} else {
mapped_ctrl_c |= mode;
}
}
- mp->m_keys = vim_strsave(keys);
+ mp->m_keys = vim_strsave(lhs);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_keylen = (int)STRLEN(mp->m_keys);
mp->m_noremap = noremap;
- mp->m_nowait = nowait;
- mp->m_silent = silent;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
mp->m_mode = mode;
- mp->m_expr = expr;
+ mp->m_expr = args->expr;
mp->m_script_ID = current_SID;
- /* add the new entry in front of the abbrlist or maphash[] list */
- if (abbrev) {
+ // add the new entry in front of the abbrlist or maphash[] list
+ if (is_abbrev) {
mp->m_next = *abbr_table;
*abbr_table = mp;
} else {
@@ -2990,11 +3030,80 @@ do_map (
}
theend:
- xfree(keys_buf);
- xfree(arg_buf);
return retval;
}
+
+/// Set or remove a mapping or an abbreviation in the current buffer, OR
+/// display (matching) mappings/abbreviations.
+///
+/// ```vim
+/// map[!] " show all key mappings
+/// map[!] {lhs} " show key mapping for {lhs}
+/// map[!] {lhs} {rhs} " set key mapping for {lhs} to {rhs}
+/// noremap[!] {lhs} {rhs} " same, but no remapping for {rhs}
+/// unmap[!] {lhs} " remove key mapping for {lhs}
+/// abbr " show all abbreviations
+/// abbr {lhs} " show abbreviations for {lhs}
+/// abbr {lhs} {rhs} " set abbreviation for {lhs} to {rhs}
+/// noreabbr {lhs} {rhs} " same, but no remapping for {rhs}
+/// unabbr {lhs} " remove abbreviation for {lhs}
+///
+/// for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING
+/// for :map! mode is INSERT + CMDLINE
+/// for :cmap mode is CMDLINE
+/// for :imap mode is INSERT
+/// for :lmap mode is LANGMAP
+/// for :nmap mode is NORMAL
+/// for :vmap mode is VISUAL + SELECTMODE
+/// for :xmap mode is VISUAL
+/// for :smap mode is SELECTMODE
+/// for :omap mode is OP_PENDING
+/// for :tmap mode is TERM_FOCUS
+///
+/// for :abbr mode is INSERT + CMDLINE
+/// for :iabbr mode is INSERT
+/// for :cabbr mode is CMDLINE
+/// ```
+///
+/// @param maptype 0 for |:map|, 1 for |:unmap|, 2 for |noremap|.
+/// @param arg C-string containing the arguments of the map/abbrev
+/// command, i.e. everything except the initial `:[X][nore]map`.
+/// - Cannot be a read-only string; it will be modified.
+/// @param mode Bitflags representing the mode in which to set the mapping.
+/// See @ref get_map_mode.
+/// @param is_abbrev True if setting an abbreviation, false otherwise.
+///
+/// @return 0 on success. On failure, will return one of the following:
+/// - 1 for invalid arguments
+/// - 2 for no match
+/// - 4 for out of mem (deprecated, WON'T HAPPEN)
+/// - 5 for entry not unique
+///
+int do_map(int maptype, char_u *arg, int mode, bool is_abbrev)
+{
+ MapArguments parsed_args;
+ int result = str_to_mapargs(arg, maptype == 1, &parsed_args);
+ switch (result) {
+ case 0:
+ break;
+ case 1:
+ result = 1; // invalid arguments
+ goto free_and_return;
+ default:
+ assert(false && "Unknown return code from str_to_mapargs!");
+ result = -1;
+ goto free_and_return;
+ } // switch
+
+ result = buf_do_map(maptype, &parsed_args, mode, is_abbrev, curbuf);
+
+free_and_return:
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ return result;
+}
+
/*
* Delete one entry from the abbrlist or maphash[].
* "mpp" is a pointer to the m_next field of the PREVIOUS entry!
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 4f548d975a..a40ea7730a 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -5,6 +5,7 @@
#include "nvim/types.h"
#include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/vim.h"
/// Values for "noremap" argument of ins_typebuf()
///
@@ -23,6 +24,39 @@ typedef enum {
FLUSH_INPUT // flush typebuf and inchar() input
} flush_buffers_T;
+/// All possible |:map-arguments| usable in a |:map| command.
+///
+/// The <special> argument has no effect on mappings and is excluded from this
+/// struct declaration. |noremap| is included, since it behaves like a map
+/// argument when used in a mapping.
+///
+/// @see mapblock_T
+struct map_arguments {
+ bool buffer;
+ bool expr;
+ bool noremap;
+ bool nowait;
+ bool script;
+ bool silent;
+ bool unique;
+
+ /// The {lhs} of the mapping.
+ ///
+ /// vim limits this to MAXMAPLEN characters, allowing us to use a static
+ /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
+ /// that {lhs} was too long and truncated.
+ char_u lhs[MAXMAPLEN + 1];
+ size_t lhs_len;
+
+ char_u *rhs; /// The {rhs} of the mapping.
+ size_t rhs_len;
+ bool rhs_is_noop; /// True when the {orig_rhs} is <nop>.
+
+ char_u *orig_rhs; /// The original text of the {rhs}.
+ size_t orig_rhs_len;
+};
+typedef struct map_arguments MapArguments;
+
#define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */
#define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */
#define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 79af4a8ce3..ec14ada3d2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -227,6 +227,7 @@ EXTERN dict_T vimvardict; /* Dictionary with v: variables */
EXTERN dict_T globvardict; /* Dictionary with g: variables */
EXTERN int did_emsg; /* set by emsg() when the message
is displayed or thrown */
+EXTERN bool called_vim_beep; // set if vim_beep() is called
EXTERN int did_emsg_syntax; /* did_emsg set because of a
syntax error */
EXTERN int called_emsg; /* always set by emsg() */
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 27e2abe1ad..4e1c7dff57 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -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);
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 662eda3c7c..a071314453 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -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 */
@@ -348,7 +349,7 @@ int ml_open(buf_T *buf)
/*
* Allocate first data block and create an empty line 1.
*/
- hp = ml_new_data(mfp, FALSE, 1);
+ hp = ml_new_data(mfp, false, 1);
if (hp->bh_bnum != 2) {
IEMSG(_("E298: Didn't get block nr 2?"));
goto error;
@@ -763,7 +764,7 @@ void ml_recover(void)
long error;
int cannot_open;
linenr_T line_count;
- int has_error;
+ bool has_error;
int idx;
int top;
int txt_start;
@@ -983,8 +984,9 @@ void ml_recover(void)
* Now that we are sure that the file is going to be recovered, clear the
* contents of the current buffer.
*/
- while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
- ml_delete((linenr_T)1, FALSE);
+ while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
+ ml_delete((linenr_T)1, false);
+ }
/*
* Try reading the original file to obtain the values of 'fileformat',
@@ -1033,8 +1035,8 @@ void ml_recover(void)
}
++error;
ml_append(lnum++, (char_u *)_("???MANY LINES MISSING"),
- (colnr_T)0, TRUE);
- } else { /* there is a block */
+ (colnr_T)0, true);
+ } else { // there is a block
pp = hp->bh_data;
if (pp->pb_id == PTR_ID) { /* it is a pointer block */
/* check line count when using pointer block first time */
@@ -1044,15 +1046,15 @@ void ml_recover(void)
if (line_count != 0) {
++error;
ml_append(lnum++, (char_u *)_("???LINE COUNT WRONG"),
- (colnr_T)0, TRUE);
+ (colnr_T)0, true);
}
}
if (pp->pb_count == 0) {
ml_append(lnum++, (char_u *)_("???EMPTY BLOCK"),
- (colnr_T)0, TRUE);
- ++error;
- } else if (idx < (int)pp->pb_count) { /* go a block deeper */
+ (colnr_T)0, true);
+ error++;
+ } else if (idx < (int)pp->pb_count) { // go a block deeper
if (pp->pb_pointer[idx].pe_bnum < 0) {
/*
* Data block with negative block number.
@@ -1072,7 +1074,7 @@ void ml_recover(void)
if (cannot_open) {
++error;
ml_append(lnum++, (char_u *)_("???LINES MISSING"),
- (colnr_T)0, TRUE);
+ (colnr_T)0, true);
}
++idx; /* get same block again for next index */
continue;
@@ -1102,23 +1104,21 @@ void ml_recover(void)
}
++error;
ml_append(lnum++, (char_u *)_("???BLOCK MISSING"),
- (colnr_T)0, TRUE);
+ (colnr_T)0, true);
} else {
- /*
- * it is a data block
- * Append all the lines in this block
- */
- has_error = FALSE;
- /*
- * check length of block
- * if wrong, use length in pointer block
- */
+ // it is a data block
+ // Append all the lines in this block
+ has_error = false;
+ // check length of block
+ // if wrong, use length in pointer block
if (page_count * mfp->mf_page_size != dp->db_txt_end) {
- ml_append(lnum++,
- (char_u *)_("??? from here until ???END lines may be messed up"),
- (colnr_T)0, TRUE);
- ++error;
- has_error = TRUE;
+ ml_append(
+ lnum++,
+ (char_u *)_("??? from here until ???END lines"
+ " may be messed up"),
+ (colnr_T)0, true);
+ error++;
+ has_error = true;
dp->db_txt_end = page_count * mfp->mf_page_size;
}
@@ -1130,12 +1130,13 @@ void ml_recover(void)
* if wrong, use count in data block
*/
if (line_count != dp->db_line_count) {
- ml_append(lnum++,
- (char_u *)_(
- "??? from here until ???END lines may have been inserted/deleted"),
- (colnr_T)0, TRUE);
- ++error;
- has_error = TRUE;
+ ml_append(
+ lnum++,
+ (char_u *)_("??? from here until ???END lines"
+ " may have been inserted/deleted"),
+ (colnr_T)0, true);
+ error++;
+ has_error = true;
}
for (i = 0; i < dp->db_line_count; ++i) {
@@ -1146,11 +1147,11 @@ void ml_recover(void)
++error;
} else
p = (char_u *)dp + txt_start;
- ml_append(lnum++, p, (colnr_T)0, TRUE);
+ ml_append(lnum++, p, (colnr_T)0, true);
+ }
+ if (has_error) {
+ ml_append(lnum++, (char_u *)_("???END"), (colnr_T)0, true);
}
- if (has_error)
- ml_append(lnum++, (char_u *)_("???END"),
- (colnr_T)0, TRUE);
}
}
}
@@ -1201,7 +1202,7 @@ void ml_recover(void)
*/
while (curbuf->b_ml.ml_line_count > lnum
&& !(curbuf->b_ml.ml_flags & ML_EMPTY))
- ml_delete(curbuf->b_ml.ml_line_count, FALSE);
+ ml_delete(curbuf->b_ml.ml_line_count, false);
curbuf->b_flags |= BF_RECOVERED;
recoverymode = FALSE;
@@ -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
{
@@ -1737,7 +1814,7 @@ char_u *
ml_get_buf (
buf_T *buf,
linenr_T lnum,
- int will_change /* line will be changed */
+ bool will_change // line will be changed
)
{
bhdr_T *hp;
@@ -1823,12 +1900,11 @@ int ml_line_alloced(void)
*
* return FAIL for failure, OK otherwise
*/
-int
-ml_append (
- linenr_T lnum, /* append after this line (can be 0) */
- char_u *line, /* text of the new line */
- colnr_T len, /* length of new line, including NUL, or 0 */
- int newfile /* flag, see above */
+int ml_append(
+ linenr_T lnum, // append after this line (can be 0)
+ char_u *line, // text of the new line
+ colnr_T len, // length of new line, including NUL, or 0
+ bool newfile // flag, see above
)
{
/* When starting up, we might still need to create the memfile */
@@ -1844,13 +1920,12 @@ ml_append (
* Like ml_append() but for an arbitrary buffer. The buffer must already have
* a memline.
*/
-int
-ml_append_buf (
+int ml_append_buf(
buf_T *buf,
- linenr_T lnum, /* append after this line (can be 0) */
- char_u *line, /* text of the new line */
- colnr_T len, /* length of new line, including NUL, or 0 */
- int newfile /* flag, see above */
+ linenr_T lnum, // append after this line (can be 0)
+ char_u *line, // text of the new line
+ colnr_T len, // length of new line, including NUL, or 0
+ bool newfile // flag, see above
)
{
if (buf->b_ml.ml_mfp == NULL)
@@ -1861,14 +1936,13 @@ ml_append_buf (
return ml_append_int(buf, lnum, line, len, newfile, FALSE);
}
-static int
-ml_append_int (
+static int ml_append_int(
buf_T *buf,
- linenr_T lnum, /* append after this line (can be 0) */
- char_u *line, /* text of the new line */
- colnr_T len, /* length of line, including NUL, or 0 */
- int newfile, /* flag, see above */
- int mark /* mark the new line */
+ linenr_T lnum, // append after this line (can be 0)
+ char_u *line, // text of the new line
+ colnr_T len, // length of line, including NUL, or 0
+ bool newfile, // flag, see above
+ int mark // mark the new line
)
{
int i;
@@ -2351,13 +2425,13 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
///
/// @param message Show "--No lines in buffer--" message.
/// @return FAIL for failure, OK otherwise
-int ml_delete(linenr_T lnum, int message)
+int ml_delete(linenr_T lnum, bool message)
{
ml_flush_line(curbuf);
return ml_delete_int(curbuf, lnum, message);
}
-static int ml_delete_int(buf_T *buf, linenr_T lnum, int message)
+static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
{
bhdr_T *hp;
memfile_T *mfp;
@@ -2677,17 +2751,15 @@ static void ml_flush_line(buf_T *buf)
/* The else case is already covered by the insert and delete */
ml_updatechunk(buf, lnum, (long)extra, ML_CHNK_UPDLINE);
} else {
- /*
- * Cannot do it in one data block: Delete and append.
- * Append first, because ml_delete_int() cannot delete the
- * last line in a buffer, which causes trouble for a buffer
- * that has only one line.
- * Don't forget to copy the mark!
- */
- /* How about handling errors??? */
- (void)ml_append_int(buf, lnum, new_line, new_len, FALSE,
- (dp->db_index[idx] & DB_MARKED));
- (void)ml_delete_int(buf, lnum, FALSE);
+ // Cannot do it in one data block: Delete and append.
+ // Append first, because ml_delete_int() cannot delete the
+ // last line in a buffer, which causes trouble for a buffer
+ // that has only one line.
+ // Don't forget to copy the mark!
+ // How about handling errors???
+ (void)ml_append_int(buf, lnum, new_line, new_len, false,
+ (dp->db_index[idx] & DB_MARKED));
+ (void)ml_delete_int(buf, lnum, false);
}
}
xfree(new_line);
@@ -2701,7 +2773,7 @@ static void ml_flush_line(buf_T *buf)
/*
* create a new, empty, data block
*/
-static bhdr_T *ml_new_data(memfile_T *mfp, int negative, int page_count)
+static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
{
assert(page_count >= 0);
bhdr_T *hp = mf_new(mfp, negative, (unsigned)page_count);
@@ -3353,17 +3425,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 +3474,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
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 523bb20738..472481bb30 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -774,15 +774,12 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes)
if (menu_name_equal(name, menu)) {
// Found menu
if (*p != NUL && menu->children == NULL) {
- if (*p != NUL) {
EMSG(_(e_notsubmenu));
return NULL;
- } else if ((menu->modes & modes) == 0x0) {
- EMSG(_(e_othermode));
- return NULL;
- }
- }
- if (*p == NUL) { // found a full match
+ } else if ((menu->modes & modes) == 0x0) {
+ EMSG(_(e_othermode));
+ return NULL;
+ } else if (*p == NUL) { // found a full match
return menu;
}
break;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index f7c248184e..cb83d6482c 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -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;
@@ -1884,7 +1880,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
}
// Concat pieces with the same highlight
ga_concat_len(&msg_ext_last_chunk, (char *)str,
- strnlen((char *)str, maxlen));
+ strnlen((char *)str, maxlen)); // -V781
return;
}
@@ -3012,6 +3008,8 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1)
} else {
keep_msg_attr = 0;
}
+ msg_ext_set_kind("wmsg");
+
if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) {
set_keep_msg(message, keep_msg_attr);
}
@@ -3352,6 +3350,7 @@ void display_confirm_msg(void)
// Avoid that 'q' at the more prompt truncates the message here.
confirm_msg_used++;
if (confirm_msg != NULL) {
+ msg_ext_set_kind("confirm");
msg_puts_attr((const char *)confirm_msg, HL_ATTR(HLF_M));
}
confirm_msg_used--;
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index e5c22e5be3..0008409731 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -745,9 +745,9 @@ open_line (
if (dir == BACKWARD)
--curwin->w_cursor.lnum;
if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) {
- if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, FALSE)
- == FAIL)
+ if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
goto theend;
+ }
// Postpone calling changed_lines(), because it would mess up folding
// with markers.
// Skip mark_adjust when adding a line after the last one, there can't
@@ -1747,8 +1747,8 @@ del_lines (
if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */
break;
- ml_delete(first, TRUE);
- ++n;
+ ml_delete(first, true);
+ n++;
/* If we delete the last line in the file, stop */
if (first > curbuf->b_ml.ml_line_count)
@@ -2248,6 +2248,7 @@ change_warning (
if (msg_row == Rows - 1)
msg_col = col;
msg_source(HL_ATTR(HLF_W));
+ msg_ext_set_kind("wmsg");
MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
msg_clr_eos();
@@ -2578,6 +2579,8 @@ void beep_flush(void)
// val is one of the BO_ values, e.g., BO_OPER
void vim_beep(unsigned val)
{
+ called_vim_beep = true;
+
if (emsg_silent == 0) {
if (!((bo_flags & val) || (bo_flags & BO_ALL))) {
if (p_vb) {
@@ -2587,8 +2590,9 @@ void vim_beep(unsigned val)
}
}
- /* When 'verbose' is set and we are sourcing a script or executing a
- * function give the user a hint where the beep comes from. */
+ // When 'debug' contains "beep" produce a message. If we are sourcing
+ // a script or executing a function give the user a hint where the beep
+ // comes from.
if (vim_strchr(p_debug, 'e') != NULL) {
msg_source(HL_ATTR(HLF_W));
msg_attr(_("Beep!"), HL_ATTR(HLF_W));
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 43fdf0a4d1..e076543614 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1524,9 +1524,9 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
/* Count screen lines that are below the window. */
scrolled += loff.height;
if (loff.lnum == curwin->w_botline
- && boff.fill == 0
- )
+ && loff.fill == 0) {
scrolled -= curwin->w_empty_rows;
+ }
}
if (boff.lnum < curbuf->b_ml.ml_line_count) {
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 5217db5b42..ca586cca29 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -4642,6 +4642,9 @@ static void nv_clear(cmdarg_T *cap)
if (!checkclearop(cap->oap)) {
/* Clear all syntax states to force resyncing. */
syn_stack_free_all(curwin->w_s);
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ wp->w_s->b_syn_slow = false;
+ }
redraw_later(CLEAR);
}
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 79a7271819..216bab4dda 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -3001,9 +3001,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
/* add a new line */
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
if (ml_append(curbuf->b_ml.ml_line_count, (char_u *)"",
- (colnr_T)1, FALSE) == FAIL)
+ (colnr_T)1, false) == FAIL) {
break;
- ++nr_lines;
+ }
+ nr_lines++;
}
/* get the old line and advance to the position to insert at */
oldp = get_cursor_line_ptr();
@@ -3190,8 +3191,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
newp = (char_u *) xmalloc((size_t)(STRLEN(ptr) + totlen + 1));
STRCPY(newp, y_array[y_size - 1]);
STRCAT(newp, ptr);
- /* insert second line */
- ml_append(lnum, newp, (colnr_T)0, FALSE);
+ // insert second line
+ ml_append(lnum, newp, (colnr_T)0, false);
xfree(newp);
oldp = ml_get(lnum);
@@ -5616,6 +5617,9 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
if (explicit_cb_reg) {
target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
+ if (writing && (cb_flags & (*name == '*' ? CB_UNNAMED : CB_UNNAMEDPLUS))) {
+ clipboard_needs_update = false;
+ }
goto end;
} else { // unnamed register: "implicit" clipboard
if (writing && clipboard_delay_update) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 4403861af4..743f6c8311 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -432,13 +432,17 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
return dest;
}
-/// Add directory to a comma-separated list of directories
+/// Adds directory `dest` to a comma-separated list of directories.
///
-/// In the added directory comma is escaped.
+/// Commas in the added directory are escaped.
+///
+/// Windows: Appends "nvim-data" instead of "nvim" if `type` is kXDGDataHome.
+///
+/// @see get_xdg_home
///
/// @param[in,out] dest Destination comma-separated array.
/// @param[in] dir Directory to append.
-/// @param[in] append_nvim If true, append "nvim" as the very first suffix.
+/// @param[in] type Decides whether to append "nvim" (Win: or "nvim-data").
/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
/// directory separator is appended. Suffix must not contain
/// commas.
@@ -452,7 +456,7 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
///
/// @return (dest + appended_characters_length)
static inline char *add_dir(char *dest, const char *const dir,
- const size_t dir_len, const bool append_nvim,
+ const size_t dir_len, const XDGVarType type,
const char *const suf1, const size_t len1,
const char *const suf2, const size_t len2)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
@@ -461,12 +465,19 @@ static inline char *add_dir(char *dest, const char *const dir,
return dest;
}
dest = strcpy_comma_escaped(dest, dir, dir_len);
+ bool append_nvim = (type == kXDGDataHome || type == kXDGConfigHome);
if (append_nvim) {
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
+#if defined(WIN32)
+ size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE);
+ memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size);
+ dest += size;
+#else
memmove(dest, "nvim", NVIM_SIZE);
dest += NVIM_SIZE;
+#endif
if (suf1 != NULL) {
*dest++ = PATHSEP;
memmove(dest, suf1, len1);
@@ -482,7 +493,10 @@ static inline char *add_dir(char *dest, const char *const dir,
return dest;
}
-/// Set &runtimepath to default value
+/// Sets &runtimepath to default value.
+///
+/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
+/// configuration and data files in the same path. #4403
static void set_runtimepath_default(void)
{
size_t rtp_size = 0;
@@ -499,8 +513,13 @@ static void set_runtimepath_default(void)
if (data_home != NULL) {
data_len = strlen(data_home);
if (data_len != 0) {
+#if defined(WIN32)
+ size_t nvim_size = (sizeof("nvim-data") - 1);
+#else
+ size_t nvim_size = NVIM_SIZE;
+#endif
rtp_size += ((data_len + memcnt(data_home, ',', data_len)
- + NVIM_SIZE + 1 + SITE_SIZE + 1
+ + nvim_size + 1 + SITE_SIZE + 1
+ !after_pathsep(data_home, data_home + data_len)) * 2
+ AFTER_SIZE + 1);
}
@@ -529,21 +548,22 @@ static void set_runtimepath_default(void)
}
char *const rtp = xmalloc(rtp_size);
char *rtp_cur = rtp;
- rtp_cur = add_dir(rtp_cur, config_home, config_len, true, NULL, 0, NULL, 0);
+ rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
+ NULL, 0, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
- rtp_cur = add_dir(rtp_cur, data_home, data_len, true, "site", SITE_SIZE,
- NULL, 0);
+ rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
+ "site", SITE_SIZE, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
true);
- rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, false, NULL, 0,
- NULL, 0);
+ rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
+ NULL, 0, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
"after", AFTER_SIZE, false);
- rtp_cur = add_dir(rtp_cur, data_home, data_len, true, "site", SITE_SIZE,
- "after", AFTER_SIZE);
+ rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
+ "site", SITE_SIZE, "after", AFTER_SIZE);
rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
false);
- rtp_cur = add_dir(rtp_cur, config_home, config_len, true,
+ rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
"after", AFTER_SIZE, NULL, 0);
// Strip trailing comma.
rtp_cur[-1] = NUL;
@@ -1813,34 +1833,30 @@ int do_set(
{
uint32_t *p = insecure_flag(opt_idx, opt_flags);
- int did_inc_secure = false;
+ const int secure_saved = secure;
// When an option is set in the sandbox, from a
// modeline or in secure mode, then deal with side
// effects in secure mode. Also when the value was
// set with the P_INSECURE flag and is not
// completely replaced.
- if (secure
+ if ((opt_flags & OPT_MODELINE)
|| sandbox != 0
- || (opt_flags & OPT_MODELINE)
|| (!value_is_replaced && (*p & P_INSECURE))) {
- did_inc_secure = true;
- secure++;
+ secure = 1;
}
- // Handle side effects, and set the global value for
- // ":set" on local options. Note: when setting 'syntax'
- // or 'filetype' autocommands may be triggered that can
- // cause havoc.
+ // Handle side effects, and set the global value
+ // for ":set" on local options. Note: when setting
+ // 'syntax' or 'filetype' autocommands may be
+ // triggered that can cause havoc.
errmsg = did_set_string_option(opt_idx, (char_u **)varp,
new_value_alloced, oldval,
errbuf, sizeof(errbuf),
opt_flags, &value_checked);
- if (did_inc_secure) {
- secure--;
- }
- }
+ secure = secure_saved;
+ }
if (errmsg == NULL) {
if (!starting) {
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
index a67e7487eb..a1020be215 100644
--- a/src/nvim/os/process.c
+++ b/src/nvim/os/process.c
@@ -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;
+}
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index f6503dfdb0..91ee45d3a9 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -80,14 +80,14 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
return ret;
}
-/// Return nvim-specific XDG directory subpath
+/// Return Nvim-specific XDG directory subpath.
///
-/// @param[in] idx XDG directory to use.
+/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
+/// configuration and data files in the same path. #4403
///
-/// @return [allocated] `{xdg_directory}/nvim`
+/// @param[in] idx XDG directory to use.
///
-/// In WIN32 get_xdg_home(kXDGDataHome) returns `{xdg_directory}/nvim-data` to
-/// avoid storing configuration and data files in the same path.
+/// @return [allocated] "{xdg_directory}/nvim"
char *get_xdg_home(const XDGVarType idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
diff --git a/src/nvim/os/stdpaths_defs.h b/src/nvim/os/stdpaths_defs.h
index 1433e0bc62..44c30df373 100644
--- a/src/nvim/os/stdpaths_defs.h
+++ b/src/nvim/os/stdpaths_defs.h
@@ -3,6 +3,7 @@
/// List of possible XDG variables
typedef enum {
+ kXDGNone = -1,
kXDGConfigHome, ///< XDG_CONFIG_HOME
kXDGDataHome, ///< XDG_DATA_HOME
kXDGCacheHome, ///< XDG_CACHE_HOME
diff --git a/src/nvim/path.c b/src/nvim/path.c
index a706e32773..67b88a861a 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1346,6 +1346,15 @@ void slash_adjust(char_u *p)
if (path_with_url((const char *)p)) {
return;
}
+
+ if (*p == '`') {
+ // don't replace backslash in backtick quoted strings
+ const size_t len = STRLEN(p);
+ if (len > 2 && *(p + len - 1) == '`') {
+ return;
+ }
+ }
+
while (*p) {
if (*p == (char_u)psepcN) {
*p = (char_u)psepc;
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 8cf09b14d7..58a0008e04 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -672,7 +672,7 @@ static int pum_set_selected(int n, int repeat)
&& (curbuf->b_p_bh[0] == 'w')) {
// Already a "wipeout" buffer, make it empty.
while (!BUFEMPTY()) {
- ml_delete((linenr_T)1, FALSE);
+ ml_delete((linenr_T)1, false);
}
} else {
// Don't want to sync undo in the current buffer.
@@ -697,11 +697,11 @@ static int pum_set_selected(int n, int repeat)
for (p = pum_array[pum_selected].pum_info; *p != NUL;) {
e = vim_strchr(p, '\n');
if (e == NULL) {
- ml_append(lnum++, p, 0, FALSE);
+ ml_append(lnum++, p, 0, false);
break;
} else {
*e = NUL;
- ml_append(lnum++, p, (int)(e - p + 1), FALSE);
+ ml_append(lnum++, p, (int)(e - p + 1), false);
*e = '\n';
p = e + 1;
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 2ee5af1ea5..29b3d19f52 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -57,19 +57,20 @@ struct dir_stack_T {
*/
typedef struct qfline_S qfline_T;
struct qfline_S {
- qfline_T *qf_next; /* pointer to next error in the list */
- qfline_T *qf_prev; /* pointer to previous error in the list */
- linenr_T qf_lnum; /* line number where the error occurred */
- int qf_fnum; /* file number for the line */
- int qf_col; /* column where the error occurred */
- int qf_nr; /* error number */
- char_u *qf_pattern; /* search pattern for the error */
- char_u *qf_text; /* description of the error */
- char_u qf_viscol; /* set to TRUE if qf_col is screen column */
- char_u qf_cleared; /* set to TRUE if line has been deleted */
- char_u qf_type; /* type of the error (mostly 'E'); 1 for
- :helpgrep */
- char_u qf_valid; /* valid error message detected */
+ qfline_T *qf_next; ///< pointer to next error in the list
+ qfline_T *qf_prev; ///< pointer to previous error in the list
+ linenr_T qf_lnum; ///< line number where the error occurred
+ int qf_fnum; ///< file number for the line
+ int qf_col; ///< column where the error occurred
+ int qf_nr; ///< error number
+ char_u *qf_module; ///< module name for this error
+ char_u *qf_pattern; ///< search pattern for the error
+ char_u *qf_text; ///< description of the error
+ char_u qf_viscol; ///< set to TRUE if qf_col is screen column
+ char_u qf_cleared; ///< set to TRUE if line has been deleted
+ char_u qf_type; ///< type of the error (mostly 'E'); 1 for
+ // :helpgrep
+ char_u qf_valid; ///< valid error message detected
};
/*
@@ -101,6 +102,7 @@ typedef struct qf_list_S {
bool qf_multiline;
bool qf_multiignore;
bool qf_multiscan;
+ long qf_changedtick;
} qf_list_T;
/// Quickfix/Location list stack definition
@@ -121,7 +123,7 @@ struct qf_info_S {
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 */
+#define FMT_PATTERNS 11 // maximum number of % recognized
/*
* Structure used to hold the info of one part of 'errorformat'
@@ -155,7 +157,8 @@ enum {
QF_OK = 1,
QF_END_OF_INPUT = 2,
QF_NOMEM = 3,
- QF_IGNORE_LINE = 4
+ QF_IGNORE_LINE = 4,
+ QF_MULTISCAN = 5,
};
typedef struct {
@@ -176,6 +179,7 @@ typedef struct {
typedef struct {
char_u *namebuf;
+ char_u *module;
char_u *errmsg;
size_t errmsglen;
long lnum;
@@ -205,6 +209,8 @@ typedef struct {
static char_u *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
+static char *e_loc_list_changed = N_("E926: Current location list was changed");
+
/// Read the errorfile "efile" into memory, line by line, building the error
/// list. Set the error list's title to qf_title.
///
@@ -247,7 +253,8 @@ static struct fmtpattern
{ 'r', ".*" },
{ 'p', "[- .]*" }, // NOLINT(whitespace/tab)
{ 'v', "\\d\\+" },
- { 's', ".\\+" }
+ { 's', ".\\+" },
+ { 'o', ".\\+" }
};
// Converts a 'errorformat' string to regular expression pattern
@@ -423,7 +430,11 @@ static efm_T * parse_efm_option(char_u *efm)
for (int round = FMT_PATTERNS - 1; round >= 0; ) {
i += STRLEN(fmt_pat[round--].pattern);
}
+#ifdef BACKSLASH_IN_FILENAME
+ i += 12; // "%f" can become twelve chars longer (see efm_to_regpat)
+#else
i += 2; // "%f" can become two chars longer
+#endif
char_u *fmtstr = xmalloc(i);
while (efm[0] != NUL) {
@@ -717,20 +728,17 @@ 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;
- int i;
int idx = 0;
char_u *tail = NULL;
- regmatch_T regmatch;
qf_list_T *qfl = &qi->qf_lists[qf_idx];
+ int status;
- // Always ignore case when looking for a matching error.
- regmatch.rm_ic = true;
-
+restofline:
// If there was no %> item start at the first pattern
if (fmt_start == NULL) {
fmt_ptr = fmt_first;
} else {
+ // Otherwise start from the last used pattern.
fmt_ptr = fmt_start;
fmt_start = NULL;
}
@@ -738,143 +746,15 @@ static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf,
// Try to match each part of 'errorformat' until we find a complete
// match or no match.
fields->valid = true;
-restofline:
for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) {
idx = fmt_ptr->prefix;
- if (qfl->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) {
- continue;
+ status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields,
+ qfl->qf_multiline, qfl->qf_multiscan,
+ &tail);
+ if (status == QF_NOMEM) {
+ return status;
}
- fields->namebuf[0] = NUL;
- fields->pattern[0] = NUL;
- if (!qfl->qf_multiscan) {
- fields->errmsg[0] = NUL;
- }
- fields->lnum = 0;
- fields->col = 0;
- fields->use_viscol = false;
- fields->enr = -1;
- fields->type = 0;
- tail = NULL;
-
- regmatch.regprog = fmt_ptr->prog;
- int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
- fmt_ptr->prog = regmatch.regprog;
- if (r) {
- if ((idx == 'C' || idx == 'Z') && !qfl->qf_multiline) {
- continue;
- }
- if (vim_strchr((char_u *)"EWI", idx) != NULL) {
- fields->type = (char_u)idx;
- } else {
- fields->type = 0;
- }
- // Extract error message data from matched line.
- // We check for an actual submatch, because "\[" and "\]" in
- // the 'errorformat' may cause the wrong submatch to be used.
- if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- // Expand ~/file and $HOME/file to full path.
- char_u c = *regmatch.endp[i];
- *regmatch.endp[i] = NUL;
- expand_env(regmatch.startp[i], fields->namebuf, CMDBUFFSIZE);
- *regmatch.endp[i] = c;
-
- if (vim_strchr((char_u *)"OPQ", idx) != NULL
- && !os_path_exists(fields->namebuf)) {
- continue;
- }
- }
- if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->enr = (int)atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->lnum = atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->col = (int)atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->type = *regmatch.startp[i];
- }
- if (fmt_ptr->flags == '+' && !qfl->qf_multiscan) { // %+
- if (linelen >= fields->errmsglen) {
- // linelen + null terminator
- fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
- fields->errmsglen = linelen + 1;
- }
- STRLCPY(fields->errmsg, linebuf, linelen + 1);
- } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len >= fields->errmsglen) {
- // len + null terminator
- fields->errmsg = xrealloc(fields->errmsg, len + 1);
- fields->errmsglen = len + 1;
- }
- STRLCPY(fields->errmsg, regmatch.startp[i], len + 1);
- }
- if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- tail = regmatch.startp[i];
- }
- if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
- char_u *match_ptr;
-
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- fields->col = 0;
- for (match_ptr = regmatch.startp[i];
- match_ptr != regmatch.endp[i]; match_ptr++) {
- fields->col++;
- if (*match_ptr == TAB) {
- fields->col += 7;
- fields->col -= fields->col % 8;
- }
- }
- fields->col++;
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->col = (int)atol((char *)regmatch.startp[i]);
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len > CMDBUFFSIZE - 5) {
- len = CMDBUFFSIZE - 5;
- }
- STRCPY(fields->pattern, "^\\V");
- xstrlcat((char *)fields->pattern, (char *)regmatch.startp[i],
- CMDBUFFSIZE+1);
- fields->pattern[len + 3] = '\\';
- fields->pattern[len + 4] = '$';
- fields->pattern[len + 5] = NUL;
- }
+ if (status == QF_OK) {
break;
}
}
@@ -882,30 +762,16 @@ restofline:
if (fmt_ptr == NULL || idx == 'D' || idx == 'X') {
if (fmt_ptr != NULL) {
- if (idx == 'D') { // enter directory
- if (*fields->namebuf == NUL) {
- EMSG(_("E379: Missing or empty directory name"));
- return QF_FAIL;
- }
- 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
- qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack);
+ // 'D' and 'X' directory specifiers.
+ status = qf_parse_dir_pfx(idx, fields, qfl);
+ if (status != QF_OK) {
+ return status;
}
}
- fields->namebuf[0] = NUL; // no match found, remove file name
- fields->lnum = 0; // don't jump to this line
- fields->valid = false;
- if (linelen >= fields->errmsglen) {
- // linelen + null terminator
- fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
- fields->errmsglen = linelen + 1;
+ status = qf_parse_line_nomatch(linebuf, linelen, fields);
+ if (status != QF_OK) {
+ return status;
}
- // copy whole line to error message
- STRLCPY(fields->errmsg, linebuf, linelen + 1);
if (fmt_ptr == NULL) {
qfl->qf_multiline = qfl->qf_multiignore = false;
}
@@ -918,63 +784,17 @@ restofline:
if (vim_strchr((char_u *)"AEWI", idx) != NULL) {
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 (!qfl->qf_multiignore) {
- qfline_T *qfprev = qfl->qf_last;
- if (qfprev == NULL) {
- return QF_FAIL;
- }
- if (*fields->errmsg) {
- size_t textlen = STRLEN(qfprev->qf_text);
- qfprev->qf_text = xrealloc(qfprev->qf_text,
- textlen + STRLEN(fields->errmsg) + 2);
- qfprev->qf_text[textlen] = '\n';
- STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg);
- }
- if (qfprev->qf_nr == -1) {
- qfprev->qf_nr = fields->enr;
- }
- if (vim_isprintc(fields->type) && !qfprev->qf_type) {
- qfprev->qf_type = fields->type; // only printable chars allowed
- }
- if (!qfprev->qf_lnum) {
- qfprev->qf_lnum = fields->lnum;
- }
- if (!qfprev->qf_col) {
- qfprev->qf_col = fields->col;
- }
- qfprev->qf_viscol = fields->use_viscol;
- if (!qfprev->qf_fnum) {
- qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
- *fields->namebuf || qfl->qf_directory
- ? fields->namebuf
- : qfl->qf_currfile && fields->valid
- ? qfl->qf_currfile : 0);
- }
+ } else if (vim_strchr((char_u *)"CZ", idx) != NULL) {
+ // continuation of multi-line msg
+ status = qf_parse_multiline_pfx(qi, qf_idx, idx, qfl, fields);
+ if (status != QF_OK) {
+ return status;
}
- if (idx == 'Z') {
- qfl->qf_multiline = qfl->qf_multiignore = false;
- }
-
- line_breakcheck();
- return QF_IGNORE_LINE;
} else if (vim_strchr((char_u *)"OPQ", idx) != NULL) {
// global file names
- fields->valid = false;
- if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) {
- if (*fields->namebuf && idx == 'P') {
- qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack,
- true);
- } else if (idx == 'Q') {
- qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack);
- }
- *fields->namebuf = NUL;
- if (tail && *tail) {
- STRMOVE(IObuff, skipwhite(tail));
- qfl->qf_multiscan = true;
- goto restofline;
- }
+ status = qf_parse_file_pfx(idx, fields, qfl, tail);
+ if (status == QF_MULTISCAN) {
+ goto restofline;
}
}
if (fmt_ptr->flags == '-') { // generally exclude this line
@@ -1034,6 +854,7 @@ qf_init_ext(
}
fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
+ fields.module = xmalloc(CMDBUFFSIZE + 1);
fields.errmsglen = CMDBUFFSIZE + 1;
fields.errmsg = xmalloc(fields.errmsglen);
fields.pattern = xmalloc(CMDBUFFSIZE + 1);
@@ -1128,6 +949,7 @@ qf_init_ext(
(*fields.namebuf || qfl->qf_directory)
? fields.namebuf : ((qfl->qf_currfile && fields.valid)
? qfl->qf_currfile : (char_u *)NULL),
+ fields.module,
0,
fields.errmsg,
fields.lnum,
@@ -1172,6 +994,7 @@ qf_init_end:
fclose(state.fd);
}
xfree(fields.namebuf);
+ xfree(fields.module);
xfree(fields.errmsg);
xfree(fields.pattern);
xfree(state.growbuf);
@@ -1187,6 +1010,8 @@ qf_init_end:
return retval;
}
+/// Set the title of the specified quickfix list. Frees the previous title.
+/// Prepends ':' to the title.
static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title)
{
xfree(qi->qf_lists[qf_idx].qf_title);
@@ -1197,10 +1022,23 @@ static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title)
char_u *p = xmallocz(len);
qi->qf_lists[qf_idx].qf_title = p;
- snprintf((char *)p, len + 1, ":%s", (char *)title);
+ xstrlcpy((char *)p, (char *)title, len + 1);
}
}
+/// The title of a quickfix/location list is set, by default, to the command
+/// that created the quickfix list with the ":" prefix.
+/// Create a quickfix list title string by prepending ":" to a user command.
+/// Returns a pointer to a static buffer with the title.
+static char_u * qf_cmdtitle(char_u *cmd)
+{
+ static char_u qftitle_str[IOSIZE];
+
+ snprintf((char *)qftitle_str, IOSIZE, ":%s", (char *)cmd);
+
+ return qftitle_str;
+}
+
// 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.
@@ -1230,9 +1068,301 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title)
qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id;
}
-/*
- * Free a location list
- */
+/// Parse the error format matches in 'regmatch' and set the values in 'fields'.
+/// fmt_ptr contains the 'efm' format specifiers/prefixes that have a match.
+/// Returns QF_OK if all the matches are successfully parsed. On failure,
+/// returns QF_FAIL or QF_NOMEM.
+static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
+ regmatch_T *regmatch, qffields_T *fields,
+ int qf_multiline, int qf_multiscan, char_u **tail)
+{
+ char_u idx = fmt_ptr->prefix;
+ int i;
+ size_t len;
+
+ if ((idx == 'C' || idx == 'Z') && !qf_multiline) {
+ return QF_FAIL;
+ }
+ if (vim_strchr((char_u *)"EWI", idx) != NULL) {
+ fields->type = idx;
+ } else {
+ fields->type = 0;
+ }
+
+ // Extract error message data from matched line.
+ // We check for an actual submatch, because "\[" and "\]" in
+ // the 'errorformat' may cause the wrong submatch to be used.
+ if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+
+ // Expand ~/file and $HOME/file to full path.
+ char_u c = *regmatch->endp[i];
+ *regmatch->endp[i] = NUL;
+ expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE);
+ *regmatch->endp[i] = c;
+
+ if (vim_strchr((char_u *)"OPQ", idx) != NULL
+ && !os_path_exists(fields->namebuf)) {
+ return QF_FAIL;
+ }
+ }
+ if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->enr = (int)atol((char *)regmatch->startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->lnum = atol((char *)regmatch->startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)regmatch->startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->type = *regmatch->startp[i];
+ }
+ if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
+ if (linelen >= fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+ } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
+ if (len >= fields->errmsglen) {
+ // len + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, len + 1);
+ fields->errmsglen = len + 1;
+ }
+ STRLCPY(fields->errmsg, regmatch->startp[i], len + 1);
+ }
+ if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ *tail = regmatch->startp[i];
+ }
+ if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = 0;
+ char_u *match_ptr;
+ for (match_ptr = regmatch->startp[i]; match_ptr != regmatch->endp[i];
+ match_ptr++) {
+ fields->col++;
+ if (*match_ptr == TAB) {
+ fields->col += 7;
+ fields->col -= fields->col % 8;
+ }
+ }
+ fields->col++;
+ fields->use_viscol = true;
+ }
+ if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)regmatch->startp[i]);
+ fields->use_viscol = true;
+ }
+ if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
+ if (len > CMDBUFFSIZE - 5) {
+ len = CMDBUFFSIZE - 5;
+ }
+ STRCPY(fields->pattern, "^\\V");
+ STRNCAT(fields->pattern, regmatch->startp[i], len);
+ fields->pattern[len + 3] = '\\';
+ fields->pattern[len + 4] = '$';
+ fields->pattern[len + 5] = NUL;
+ }
+ if ((i = (int)fmt_ptr->addr[10]) > 0) { // %o
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
+ if (len > CMDBUFFSIZE) {
+ len = CMDBUFFSIZE;
+ }
+ STRNCAT(fields->module, regmatch->startp[i], len);
+ }
+
+ return QF_OK;
+}
+
+/// Parse an error line in 'linebuf' using a single error format string in
+/// 'fmt_ptr->prog' and return the matching values in 'fields'.
+/// Returns QF_OK if the efm format matches completely and the fields are
+/// successfully copied. Otherwise returns QF_FAIL or QF_NOMEM.
+static int qf_parse_get_fields(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
+ qffields_T *fields, int qf_multiline,
+ int qf_multiscan, char_u **tail)
+{
+ regmatch_T regmatch;
+ int status = QF_FAIL;
+ int r;
+
+ if (qf_multiscan && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL) {
+ return QF_FAIL;
+ }
+
+ fields->namebuf[0] = NUL;
+ fields->module[0] = NUL;
+ fields->pattern[0] = NUL;
+ if (!qf_multiscan) {
+ fields->errmsg[0] = NUL;
+ }
+ fields->lnum = 0;
+ fields->col = 0;
+ fields->use_viscol = false;
+ fields->enr = -1;
+ fields->type = 0;
+ *tail = NULL;
+
+ // Always ignore case when looking for a matching error.
+ regmatch.rm_ic = true;
+ regmatch.regprog = fmt_ptr->prog;
+ r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
+ fmt_ptr->prog = regmatch.regprog;
+ if (r) {
+ status = qf_parse_match(linebuf, linelen, fmt_ptr, &regmatch, fields,
+ qf_multiline, qf_multiscan, tail);
+ }
+
+ return status;
+}
+
+/// Parse directory error format prefixes (%D and %X).
+/// Push and pop directories from the directory stack when scanning directory
+/// names.
+static int qf_parse_dir_pfx(int idx, qffields_T *fields, qf_list_T *qfl)
+{
+ if (idx == 'D') { // enter directory
+ if (*fields->namebuf == NUL) {
+ EMSG(_("E379: Missing or empty directory name"));
+ return QF_FAIL;
+ }
+ 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
+ qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack);
+ }
+
+ return QF_OK;
+}
+
+/// Parse global file name error format prefixes (%O, %P and %Q).
+static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl,
+ char_u *tail)
+{
+ fields->valid = false;
+ if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) {
+ if (*fields->namebuf && idx == 'P') {
+ qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack,
+ true);
+ } else if (idx == 'Q') {
+ qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack);
+ }
+ *fields->namebuf = NUL;
+ if (tail && *tail) {
+ STRMOVE(IObuff, skipwhite(tail));
+ qfl->qf_multiscan = true;
+ return QF_MULTISCAN;
+ }
+ }
+
+ return QF_OK;
+}
+
+/// Parse a non-error line (a line which doesn't match any of the error
+/// format in 'efm').
+static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen,
+ qffields_T *fields)
+{
+ fields->namebuf[0] = NUL; // no match found, remove file name
+ fields->lnum = 0; // don't jump to this line
+ fields->valid = false;
+ if (linelen >= fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ // copy whole line to error message
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+
+ return QF_OK;
+}
+
+/// Parse multi-line error format prefixes (%C and %Z)
+static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
+ qf_list_T *qfl, qffields_T *fields)
+{
+ if (!qfl->qf_multiignore) {
+ qfline_T *qfprev = qfl->qf_last;
+
+ if (qfprev == NULL) {
+ return QF_FAIL;
+ }
+ if (*fields->errmsg && !qfl->qf_multiignore) {
+ size_t textlen = strlen((char *)qfprev->qf_text);
+ size_t errlen = strlen((char *)fields->errmsg);
+ qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2);
+ qfprev->qf_text[textlen] = '\n';
+ STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg);
+ }
+ if (qfprev->qf_nr == -1) {
+ qfprev->qf_nr = fields->enr;
+ }
+ if (vim_isprintc(fields->type) && !qfprev->qf_type) {
+ // only printable chars allowed
+ qfprev->qf_type = fields->type;
+ }
+
+ if (!qfprev->qf_lnum) {
+ qfprev->qf_lnum = fields->lnum;
+ }
+ if (!qfprev->qf_col) {
+ qfprev->qf_col = fields->col;
+ }
+ qfprev->qf_viscol = fields->use_viscol;
+ if (!qfprev->qf_fnum) {
+ qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
+ *fields->namebuf || qfl->qf_directory
+ ? fields->namebuf
+ : qfl->qf_currfile && fields->valid
+ ? qfl->qf_currfile : 0);
+ }
+ }
+ if (idx == 'Z') {
+ qfl->qf_multiline = qfl->qf_multiignore = false;
+ }
+ line_breakcheck();
+
+ return QF_IGNORE_LINE;
+}
+
+/// Free a location list.
static void ll_free_all(qf_info_T **pqi)
{
int i;
@@ -1252,6 +1382,7 @@ static void ll_free_all(qf_info_T **pqi)
}
}
+/// Free all the quickfix/location lists in the stack.
void qf_free_all(win_T *wp)
{
int i;
@@ -1273,6 +1404,7 @@ void qf_free_all(win_T *wp)
/// @param qf_idx list index
/// @param dir optional directory name
/// @param fname file name or NULL
+/// @param module module name or NULL
/// @param bufnum buffer number or zero
/// @param mesg message
/// @param lnum line number
@@ -1285,9 +1417,9 @@ void qf_free_all(win_T *wp)
///
/// @returns OK or FAIL.
static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
- int bufnum, char_u *mesg, long lnum, int col,
- char_u vis_col, char_u *pattern, int nr, char_u type,
- char_u valid)
+ char_u *module, int bufnum, char_u *mesg, long lnum,
+ int col, char_u vis_col, char_u *pattern, int nr,
+ char_u type, char_u valid)
{
qfline_T *qfp = xmalloc(sizeof(qfline_T));
qfline_T **lastp; // pointer to qf_last or NULL
@@ -1312,9 +1444,15 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
} else {
qfp->qf_pattern = vim_strsave(pattern);
}
+ if (module == NULL || *module == NUL) {
+ qfp->qf_module = NULL;
+ } else {
+ qfp->qf_module = vim_strsave(module);
+ }
qfp->qf_nr = nr;
- if (type != 1 && !vim_isprintc(type)) /* only printable chars allowed */
+ if (type != 1 && !vim_isprintc(type)) { // only printable chars allowed
type = 0;
+ }
qfp->qf_type = (char_u)type;
qfp->qf_valid = valid;
@@ -1443,6 +1581,7 @@ void copy_loclist(win_T *from, win_T *to)
to->w_llist->qf_curlist,
NULL,
NULL,
+ from_qfp->qf_module,
0,
from_qfp->qf_text,
from_qfp->qf_lnum,
@@ -1473,6 +1612,7 @@ void copy_loclist(win_T *from, win_T *to)
// Assign a new ID for the location list
to_qfl->qf_id = ++last_qf_id;
+ to_qfl->qf_changedtick = 0L;
/* When no valid entries are present in the list, qf_ptr points to
* the first item in the list */
@@ -1699,6 +1839,27 @@ static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename)
return ds_ptr == NULL ? NULL : ds_ptr->dirname;
}
+/// Returns true, if a quickfix/location list with the given identifier exists.
+static bool qflist_valid(win_T *wp, unsigned int qf_id)
+{
+ qf_info_T *qi = &ql_info;
+
+ if (wp) {
+ qi = GET_LOC_LIST(wp);
+ if (!qi) {
+ return false;
+ }
+ }
+
+ for (int i = 0; i < qi->qf_listcount; i++) {
+ if (qi->qf_lists[i].qf_id == qf_id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
/// When loading a file from the quickfix, the auto commands may modify it.
/// This may invalidate the current quickfix entry. This function checks
/// whether a entry is still present in the quickfix.
@@ -2050,19 +2211,28 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
}
} else {
int old_qf_curlist = qi->qf_curlist;
+ unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
GETF_SETMARK | GETF_SWITCH, forceit);
- if (qi != &ql_info && !win_valid_any_tab(oldwin)) {
- EMSG(_("E924: Current window was closed"));
- *abort = true;
- *opened_window = false;
+
+ if (qi != &ql_info) {
+ // Location list. Check whether the associated window is still
+ // present and the list is still valid.
+ if (!win_valid_any_tab(oldwin)) {
+ EMSG(_("E924: Current window was closed"));
+ *abort = true;
+ *opened_window = false;
+ } else if (!qflist_valid(oldwin, save_qfid)) {
+ EMSG(_(e_loc_list_changed));
+ *abort = true;
+ }
} else if (old_qf_curlist != qi->qf_curlist
|| !is_qf_entry_present(qi, qf_ptr)) {
if (qi == &ql_info) {
EMSG(_("E925: Current quickfix was changed"));
} else {
- EMSG(_("E926: Current location list was changed"));
+ EMSG(_(e_loc_list_changed));
}
*abort = true;
}
@@ -2360,17 +2530,22 @@ void qf_list(exarg_T *eap)
break;
fname = NULL;
- if (qfp->qf_fnum != 0
- && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
- fname = buf->b_fname;
- if (qfp->qf_type == 1) /* :helpgrep */
- fname = path_tail(fname);
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i,
+ (char *)qfp->qf_module);
+ } else {
+ if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
+ fname = buf->b_fname;
+ if (qfp->qf_type == 1) { // :helpgrep
+ fname = path_tail(fname);
+ }
+ }
+ if (fname == NULL) {
+ snprintf((char *)IObuff, IOSIZE, "%2d", i);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname);
+ }
}
- if (fname == NULL)
- sprintf((char *)IObuff, "%2d", i);
- else
- vim_snprintf((char *)IObuff, IOSIZE, "%2d %s",
- i, (char *)fname);
msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index
? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D));
if (qfp->qf_lnum == 0) {
@@ -2431,6 +2606,8 @@ static void qf_fmt_text(char_u *text, char_u *buf, int bufsize)
buf[i] = NUL;
}
+/// Display information (list number, list size and the title) about a
+/// quickfix/location list.
static void qf_msg(qf_info_T *qi, int which, char *lead)
{
char *title = (char *)qi->qf_lists[which].qf_title;
@@ -2500,6 +2677,7 @@ void qf_age(exarg_T *eap)
qf_update_buffer(qi, NULL);
}
+/// Display the information about all the quickfix/location lists in the stack.
void qf_history(exarg_T *eap)
{
qf_info_T *qi = &ql_info;
@@ -2531,9 +2709,10 @@ static void qf_free_items(qf_info_T *qi, int idx)
qfp = qfl->qf_start;
qfpnext = qfp->qf_next;
if (!stop) {
+ xfree(qfp->qf_module);
xfree(qfp->qf_text);
- stop = (qfp == qfpnext);
xfree(qfp->qf_pattern);
+ stop = (qfp == qfpnext);
xfree(qfp);
if (stop) {
// Somehow qf_count may have an incorrect value, set it to 1
@@ -2575,6 +2754,7 @@ static void qf_free(qf_info_T *qi, int idx)
tv_free(qfl->qf_ctx);
qfl->qf_ctx = NULL;
qfl->qf_id = 0;
+ qfl->qf_changedtick = 0L;
}
/*
@@ -2944,10 +3124,8 @@ static int is_qf_win(win_T *win, qf_info_T *qi)
return FALSE;
}
-/*
- * Find a window displaying the quickfix/location list 'qi'
- * Searches in only the windows opened in the current tab.
- */
+/// Find a window displaying the quickfix/location list 'qi'
+/// Only searches in the current tabpage.
static win_T *qf_find_win(qf_info_T *qi)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
@@ -3075,9 +3253,12 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
lnum = buf->b_ml.ml_line_count;
}
while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) {
- if (qfp->qf_fnum != 0
- && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
- && errbuf->b_fname != NULL) {
+ if (qfp->qf_module != NULL) {
+ STRCPY(IObuff, qfp->qf_module);
+ len = (int)STRLEN(IObuff);
+ } else if (qfp->qf_fnum != 0
+ && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
+ && errbuf->b_fname != NULL) {
if (qfp->qf_type == 1) { // :helpgrep
STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff));
} else {
@@ -3165,6 +3346,11 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
KeyTyped = old_KeyTyped;
}
+static void qf_list_changed(qf_info_T *qi, int qf_idx)
+{
+ qi->qf_lists[qf_idx].qf_changedtick++;
+}
+
/*
* Return TRUE when using ":vimgrep" for ":grep".
*/
@@ -3248,17 +3434,21 @@ void ex_make(exarg_T *eap)
res = qf_init(wp, fname, (eap->cmdidx != CMD_make
&& eap->cmdidx != CMD_lmake) ? p_gefm : p_efm,
(eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd),
- *eap->cmdlinep, enc);
+ qf_cmdtitle(*eap->cmdlinep), enc);
if (wp != NULL) {
qi = GET_LOC_LIST(wp);
}
+ if (res >= 0 && qi != NULL) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
- curbuf->b_fname, TRUE, curbuf);
- if (qi->qf_curlist < qi->qf_listcount)
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true,
+ curbuf);
+ if (qi != NULL && qi->qf_curlist < qi->qf_listcount) {
res = qi->qf_lists[qi->qf_curlist].qf_count;
- else
+ } else {
res = 0;
+ }
}
if (res > 0 && !eap->forceit)
qf_jump(qi, 0, 0, FALSE); /* display first error */
@@ -3602,19 +3792,218 @@ void ex_cfile(exarg_T *eap)
// first error.
// :caddfile adds to an existing quickfix list. If there is no
// quickfix list then a new list is created.
- if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile
- && eap->cmdidx != CMD_laddfile),
- *eap->cmdlinep, enc) > 0
- && (eap->cmdidx == CMD_cfile
- || eap->cmdidx == CMD_lfile)) {
- if (au_name != NULL)
- apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf);
- if (wp != NULL)
- qi = GET_LOC_LIST(wp);
- qf_jump(qi, 0, 0, eap->forceit); /* display first error */
+ int res = qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile
+ && eap->cmdidx != CMD_laddfile),
+ qf_cmdtitle(*eap->cmdlinep), enc);
+ if (wp != NULL) {
+ qi = GET_LOC_LIST(wp);
+ }
+ if (res >= 0 && qi != NULL) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
+ unsigned save_qfid = 0;
+ if (qi != NULL) {
+ save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ }
+ if (au_name != NULL) {
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf);
+ }
+ // Autocmd might have freed the quickfix/location list. Check whether it is
+ // still valid
+ if (qi != NULL && !qflist_valid(wp, save_qfid)) {
+ return;
+ }
+ if (res > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) {
+ qf_jump(qi, 0, 0, eap->forceit); // display first error
+ }
+}
+
+/// Return the vimgrep autocmd name.
+static char_u *vgr_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_vimgrep: return (char_u *)"vimgrep";
+ case CMD_lvimgrep: return (char_u *)"lvimgrep";
+ case CMD_vimgrepadd: return (char_u *)"vimgrepadd";
+ case CMD_lvimgrepadd: return (char_u *)"lvimgrepadd";
+ case CMD_grep: return (char_u *)"grep";
+ case CMD_lgrep: return (char_u *)"lgrep";
+ case CMD_grepadd: return (char_u *)"grepadd";
+ case CMD_lgrepadd: return (char_u *)"lgrepadd";
+ default: return NULL;
+ }
+}
+
+/// Initialize the regmatch used by vimgrep for pattern "s".
+static void vgr_init_regmatch(regmmatch_T *regmatch, char_u *s)
+{
+ // Get the search pattern: either white-separated or enclosed in //.
+ regmatch->regprog = NULL;
+
+ if (s == NULL || *s == NUL) {
+ // Pattern is empty, use last search pattern.
+ if (last_search_pat() == NULL) {
+ EMSG(_(e_noprevre));
+ return;
+ }
+ regmatch->regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
+ } else {
+ regmatch->regprog = vim_regcomp(s, RE_MAGIC);
+ }
+
+ regmatch->rmm_ic = p_ic;
+ regmatch->rmm_maxcol = 0;
+}
+
+
+/// Display a file name when vimgrep is running.
+static void vgr_display_fname(char_u *fname)
+{
+ msg_start();
+ char_u *p = msg_strtrunc(fname, true);
+ if (p == NULL) {
+ msg_outtrans(fname);
} else {
- if (au_name != NULL)
- apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf);
+ msg_outtrans(p);
+ xfree(p);
+ }
+ msg_clr_eos();
+ msg_didout = false; // overwrite this message
+ msg_nowait = true; // don't wait for this message
+ msg_col = 0;
+ ui_flush();
+}
+
+/// Load a dummy buffer to search for a pattern using vimgrep.
+static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start,
+ char_u *dirname_now)
+{
+ char_u *save_ei = NULL;
+
+ // Don't do Filetype autocommands to avoid loading syntax and
+ // indent scripts, a great speed improvement.
+ long save_mls = p_mls;
+ p_mls = 0;
+
+ // Load file into a buffer, so that 'fileencoding' is detected,
+ // autocommands applied, etc.
+ buf_T *buf = load_dummy_buffer(fname, dirname_start, dirname_now);
+
+ p_mls = save_mls;
+ au_event_restore(save_ei);
+
+ return buf;
+}
+
+/// Check whether a quickfix/location list is valid. Autocmds may remove or
+/// change a quickfix list when vimgrep is running. If the list is not found,
+/// create a new list.
+static bool vgr_qflist_valid(qf_info_T *qi, unsigned save_qfid,
+ qfline_T *cur_qf_start, int loclist_cmd,
+ char_u *title)
+{
+ if (loclist_cmd) {
+ // Verify that the location list is still valid. An autocmd might have
+ // freed the location list.
+ if (!qflist_valid(curwin, save_qfid)) {
+ EMSG(_(e_loc_list_changed));
+ return false;
+ }
+ }
+ if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) {
+ int idx;
+ // Autocommands changed the quickfix list. Find the one we were using
+ // and restore it.
+ for (idx = 0; idx < LISTCOUNT; idx++) {
+ if (cur_qf_start == qi->qf_lists[idx].qf_start) {
+ qi->qf_curlist = idx;
+ break;
+ }
+ }
+ if (idx == LISTCOUNT) {
+ // List cannot be found, create a new one.
+ qf_new_list(qi, title);
+ }
+ }
+
+ return true;
+}
+
+
+/// Search for a pattern in all the lines in a buffer and add the matching lines
+/// to a quickfix list.
+static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
+ regmmatch_T *regmatch, long tomatch,
+ int duplicate_name, int flags)
+{
+ bool found_match = false;
+
+ for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; lnum++) {
+ colnr_T col = 0;
+ while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
+ NULL) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qi,
+ qi->qf_curlist,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum,
+ false),
+ regmatch->startpos[0].lnum + lnum,
+ regmatch->startpos[0].col + 1,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true // valid
+ ) == FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
+ break;
+ }
+ col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
+ if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ break;
+ }
+ }
+ line_breakcheck();
+ if (got_int) {
+ break;
+ }
+ }
+
+ return found_match;
+}
+
+/// Jump to the first match and update the directory.
+static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
+ buf_T *first_match_buf, char_u *target_dir)
+{
+ buf_T *buf = curbuf;
+ qf_jump(qi, 0, 0, forceit);
+ if (buf != curbuf) {
+ // If we jumped to another buffer redrawing will already be
+ // taken care of.
+ *redraw_for_dummy = false;
+ }
+
+ // Jump to the directory used after loading the buffer.
+ if (curbuf == first_match_buf && target_dir != NULL) {
+ exarg_T ea = {
+ .arg = target_dir,
+ .cmdidx = CMD_lcd,
+ };
+ ex_cd(&ea);
}
}
@@ -3634,8 +4023,9 @@ void ex_vimgrep(exarg_T *eap)
char_u *p;
int fi;
qf_info_T *qi = &ql_info;
+ int loclist_cmd = false;
qfline_T *cur_qf_start;
- long lnum;
+ win_T *wp;
buf_T *buf;
int duplicate_name = FALSE;
int using_dummy;
@@ -3643,28 +4033,15 @@ void ex_vimgrep(exarg_T *eap)
int found_match;
buf_T *first_match_buf = NULL;
time_t seconds = 0;
- long save_mls;
- char_u *save_ei = NULL;
aco_save_T aco;
int flags = 0;
- colnr_T col;
long tomatch;
char_u *dirname_start = NULL;
char_u *dirname_now = NULL;
char_u *target_dir = NULL;
char_u *au_name = NULL;
- switch (eap->cmdidx) {
- case CMD_vimgrep: au_name = (char_u *)"vimgrep"; break;
- case CMD_lvimgrep: au_name = (char_u *)"lvimgrep"; break;
- case CMD_vimgrepadd: au_name = (char_u *)"vimgrepadd"; break;
- case CMD_lvimgrepadd: au_name = (char_u *)"lvimgrepadd"; break;
- case CMD_grep: au_name = (char_u *)"grep"; break;
- case CMD_lgrep: au_name = (char_u *)"lgrep"; break;
- case CMD_grepadd: au_name = (char_u *)"grepadd"; break;
- case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break;
- default: break;
- }
+ au_name = vgr_get_auname(eap->cmdidx);
if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
@@ -3677,6 +4054,7 @@ void ex_vimgrep(exarg_T *eap)
|| eap->cmdidx == CMD_lgrepadd
|| eap->cmdidx == CMD_lvimgrepadd) {
qi = ll_get_or_alloc_list(curwin);
+ loclist_cmd = true;
}
if (eap->addr_count > 0)
@@ -3686,28 +4064,17 @@ void ex_vimgrep(exarg_T *eap)
/* Get the search pattern: either white-separated or enclosed in // */
regmatch.regprog = NULL;
- char_u *title = vim_strsave(*eap->cmdlinep);
+ char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
p = skip_vimgrep_pat(eap->arg, &s, &flags);
if (p == NULL) {
EMSG(_(e_invalpat));
goto theend;
}
- if (s == NULL || *s == NUL) {
- // Pattern is empty, use last search pattern.
- if (last_search_pat() == NULL) {
- EMSG(_(e_noprevre));
- goto theend;
- }
- regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
- } else {
- regmatch.regprog = vim_regcomp(s, RE_MAGIC);
- }
-
- if (regmatch.regprog == NULL)
+ vgr_init_regmatch(&regmatch, s);
+ if (regmatch.regprog == NULL) {
goto theend;
- regmatch.rmm_ic = p_ic;
- regmatch.rmm_maxcol = 0;
+ }
p = skipwhite(p);
if (*p == NUL) {
@@ -3737,8 +4104,9 @@ void ex_vimgrep(exarg_T *eap)
* ":lcd %:p:h" changes the meaning of short path names. */
os_dirname(dirname_start, MAXPATHL);
- /* Remember the value of qf_start, so that we can check for autocommands
- * changing the current quickfix list. */
+ // Remember the current values of the quickfix list and qf_start, so that
+ // we can check for autocommands changing the current quickfix list.
+ unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
seconds = (time_t)0;
@@ -3748,19 +4116,7 @@ void ex_vimgrep(exarg_T *eap)
/* Display the file name every second or so, show the user we are
* working on it. */
seconds = time(NULL);
- msg_start();
- p = msg_strtrunc(fname, TRUE);
- if (p == NULL)
- msg_outtrans(fname);
- else {
- msg_outtrans(p);
- xfree(p);
- }
- msg_clr_eos();
- msg_didout = FALSE; /* overwrite this message */
- msg_nowait = TRUE; /* don't wait for this message */
- msg_col = 0;
- ui_flush();
+ vgr_display_fname(fname);
}
buf = buflist_findname_exp(fnames[fi]);
@@ -3770,88 +4126,28 @@ void ex_vimgrep(exarg_T *eap)
using_dummy = TRUE;
redraw_for_dummy = TRUE;
- /* Don't do Filetype autocommands to avoid loading syntax and
- * indent scripts, a great speed improvement. */
- save_ei = au_event_disable(",Filetype");
- /* Don't use modelines here, it's useless. */
- save_mls = p_mls;
- p_mls = 0;
-
- /* Load file into a buffer, so that 'fileencoding' is detected,
- * autocommands applied, etc. */
- buf = load_dummy_buffer(fname, dirname_start, dirname_now);
-
- p_mls = save_mls;
- au_event_restore(save_ei);
- } else
- /* Use existing, loaded buffer. */
- using_dummy = FALSE;
-
- if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) {
- int idx;
+ buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now);
+ } else {
+ // Use existing, loaded buffer.
+ using_dummy = false;
+ }
- /* Autocommands changed the quickfix list. Find the one we were
- * using and restore it. */
- for (idx = 0; idx < LISTCOUNT; ++idx)
- if (cur_qf_start == qi->qf_lists[idx].qf_start) {
- qi->qf_curlist = idx;
- break;
- }
- if (idx == LISTCOUNT) {
- /* List cannot be found, create a new one. */
- qf_new_list(qi, *eap->cmdlinep);
- cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
- }
+ // Check whether the quickfix list is still valid
+ if (!vgr_qflist_valid(qi, save_qfid, cur_qf_start, loclist_cmd,
+ *eap->cmdlinep)) {
+ goto theend;
}
+ cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
if (buf == NULL) {
if (!got_int)
smsg(_("Cannot open file \"%s\""), fname);
} else {
- /* Try for a match in all lines of the buffer.
- * For ":1vimgrep" look for first match only. */
- found_match = FALSE;
- for (lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0;
- ++lnum) {
- col = 0;
- while (vim_regexec_multi(&regmatch, curwin, buf, lnum,
- col, NULL, NULL) > 0) {
- // Pass the buffer number so that it gets used even for a
- // dummy buffer, unless duplicate_name is set, then the
- // buffer will be wiped out below.
- if (qf_add_entry(qi,
- qi->qf_curlist,
- NULL, // dir
- fname,
- duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf,
- regmatch.startpos[0].lnum + lnum, false),
- regmatch.startpos[0].lnum + lnum,
- regmatch.startpos[0].col + 1,
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 0, // type
- true) // valid
- == FAIL) {
- got_int = true;
- break;
- }
- found_match = TRUE;
- if (--tomatch == 0)
- break;
- if ((flags & VGR_GLOBAL) == 0
- || regmatch.endpos[0].lnum > 0)
- break;
- col = regmatch.endpos[0].col
- + (col == regmatch.endpos[0].col);
- if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, FALSE)))
- break;
- }
- line_breakcheck();
- if (got_int)
- break;
- }
+ // Try for a match in all lines of the buffer.
+ // For ":1vimgrep" look for first match only.
+ found_match = vgr_match_buflines(qi, fname, buf, &regmatch, tomatch,
+ duplicate_name, flags);
+
cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
if (using_dummy) {
@@ -3914,6 +4210,7 @@ void ex_vimgrep(exarg_T *eap)
qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
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;
+ qf_list_changed(qi, qi->qf_curlist);
qf_update_buffer(qi, NULL);
@@ -3921,24 +4218,18 @@ void ex_vimgrep(exarg_T *eap)
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, TRUE, curbuf);
+ // The QuickFixCmdPost autocmd may free the quickfix list. Check the list
+ // is still valid.
+ wp = loclist_cmd ? curwin : NULL;
+ if (!qflist_valid(wp, save_qfid)) {
+ goto theend;
+ }
+
/* Jump to first match. */
if (qi->qf_lists[qi->qf_curlist].qf_count > 0) {
if ((flags & VGR_NOJUMP) == 0) {
- buf = curbuf;
- qf_jump(qi, 0, 0, eap->forceit);
- if (buf != curbuf)
- /* If we jumped to another buffer redrawing will already be
- * taken care of. */
- redraw_for_dummy = FALSE;
-
- /* Jump to the directory used after loading the buffer. */
- if (curbuf == first_match_buf && target_dir != NULL) {
- exarg_T ea;
-
- ea.arg = target_dir;
- ea.cmdidx = CMD_lcd;
- ex_cd(&ea);
- }
+ vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
+ target_dir);
}
} else
EMSG2(_(e_nomatch2), s);
@@ -3969,10 +4260,10 @@ static void restore_start_dir(char_u *dirname_start)
if (STRCMP(dirname_start, dirname_now) != 0) {
/* If the directory has changed, change it back by building up an
* appropriate ex command and executing it. */
- exarg_T ea;
-
- ea.arg = dirname_start;
- ea.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd;
+ exarg_T ea = {
+ .arg = dirname_start,
+ .cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd,
+ };
ex_cd(&ea);
}
xfree(dirname_now);
@@ -4168,6 +4459,10 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
|| (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol)
== FAIL)
|| (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL)
+ || tv_dict_add_str(dict, S_LEN("module"),
+ (qfp->qf_module == NULL
+ ? ""
+ : (const char *)qfp->qf_module)) == FAIL
|| tv_dict_add_str(dict, S_LEN("pattern"),
(qfp->qf_pattern == NULL
? ""
@@ -4203,10 +4498,12 @@ enum {
QF_GETLIST_ID = 0x20,
QF_GETLIST_IDX = 0x40,
QF_GETLIST_SIZE = 0x80,
- QF_GETLIST_ALL = 0xFF
+ QF_GETLIST_TICK = 0x100,
+ QF_GETLIST_ALL = 0x1FF
};
-// Parse text from 'di' and return the quickfix list items
+/// Parse text from 'di' and return the quickfix list items.
+/// Existing quickfix lists are not modified.
static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
{
int status = FAIL;
@@ -4256,34 +4553,72 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid)
return -1;
}
-/// 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.
-int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
+/// Return the quickfix/location list window identifier in the current tabpage.
+static int qf_winid(qf_info_T *qi)
{
- 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);
+ // The quickfix window can be opened even if the quickfix list is not set
+ // using ":copen". This is not true for location lists.
+ if (qi == NULL) {
+ return 0;
}
+ win_T *win = qf_find_win(qi);
+ if (win != NULL) {
+ return win->handle;
+ }
+ return 0;
+}
- if (wp != NULL) {
- qi = GET_LOC_LIST(wp);
+/// Convert the keys in 'what' to quickfix list property flags.
+static int qf_getprop_keys2flags(dict_T *what)
+{
+ int flags = QF_GETLIST_NONE;
+
+ if (tv_dict_find(what, S_LEN("all")) != NULL) {
+ flags |= QF_GETLIST_ALL;
}
- // 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;
+ if (tv_dict_find(what, S_LEN("title")) != NULL) {
+ flags |= QF_GETLIST_TITLE;
+ }
+ if (tv_dict_find(what, S_LEN("nr")) != NULL) {
+ flags |= QF_GETLIST_NR;
+ }
+ 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("id")) != NULL) {
+ flags |= QF_GETLIST_ID;
+ }
+ if (tv_dict_find(what, S_LEN("items")) != NULL) {
+ flags |= QF_GETLIST_ITEMS;
+ }
+ if (tv_dict_find(what, S_LEN("idx")) != NULL) {
+ flags |= QF_GETLIST_IDX;
+ }
+ if (tv_dict_find(what, S_LEN("size")) != NULL) {
+ flags |= QF_GETLIST_SIZE;
+ }
+ if (tv_dict_find(what, S_LEN("changedtick")) != NULL) {
+ flags |= QF_GETLIST_TICK;
}
- int status = OK;
- int flags = QF_GETLIST_NONE;
+ return flags;
+}
+
+/// Return the quickfix list index based on 'nr' or 'id' in 'what'.
+///
+/// If 'nr' and 'id' are not present in 'what' then return the current
+/// quickfix list index.
+/// If 'nr' is zero then return the current quickfix list index.
+/// If 'nr' is '$' then return the last quickfix list index.
+/// If 'id' is present then return the index of the quickfix list with that id.
+/// If 'id' is zero then return the quickfix list index specified by 'nr'.
+/// Return -1, if quickfix list is not present or if the stack is empty.
+static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what)
+{
+ dictitem_T *di = NULL;
int qf_idx = qi->qf_curlist; // default is the current list
if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) {
@@ -4293,7 +4628,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
if (di->di_tv.vval.v_number != 0) {
qf_idx = (int)di->di_tv.vval.v_number - 1;
if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
- return FAIL;
+ qf_idx = -1;
}
}
} else if (di->di_tv.v_type == VAR_STRING
@@ -4301,9 +4636,8 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
// Get the last quickfix list number
qf_idx = qi->qf_listcount - 1;
} else {
- return FAIL;
+ qf_idx = -1;
}
- flags |= QF_GETLIST_NR;
}
if ((di = tv_dict_find(what, S_LEN("id"))) != NULL) {
@@ -4312,91 +4646,160 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
// For zero, use the current list or the list specifed by 'nr'
if (di->di_tv.vval.v_number != 0) {
qf_idx = qf_id2nr(qi, (unsigned)di->di_tv.vval.v_number);
- if (qf_idx == -1) {
- return FAIL; // List not found
- }
}
- flags |= QF_GETLIST_ID;
} else {
- return FAIL;
+ qf_idx = -1;
}
}
- if (tv_dict_find(what, S_LEN("all")) != NULL) {
- flags |= QF_GETLIST_ALL;
+ return qf_idx;
+}
+
+/// Return default values for quickfix list properties in retdict.
+static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
+{
+ int status = OK;
+
+ if (flags & QF_GETLIST_TITLE) {
+ status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)"");
}
- if (tv_dict_find(what, S_LEN("title")) != NULL) {
- flags |= QF_GETLIST_TITLE;
+ if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
+ list_T *l = tv_list_alloc(kListLenMayKnow);
+ status = tv_dict_add_list(retdict, S_LEN("items"), l);
}
- if (tv_dict_find(what, S_LEN("winid")) != NULL) {
- flags |= QF_GETLIST_WINID;
+ if ((status == OK) && (flags & QF_GETLIST_NR)) {
+ status = tv_dict_add_nr(retdict, S_LEN("nr"), 0);
}
- if (tv_dict_find(what, S_LEN("context")) != NULL) {
- flags |= QF_GETLIST_CONTEXT;
+ if ((status == OK) && (flags & QF_GETLIST_WINID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
- if (tv_dict_find(what, S_LEN("items")) != NULL) {
- flags |= QF_GETLIST_ITEMS;
+ if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
+ status = tv_dict_add_str(retdict, S_LEN("context"), (const char *)"");
}
- if (tv_dict_find(what, S_LEN("idx")) != NULL) {
- flags |= QF_GETLIST_IDX;
+ if ((status == OK) && (flags & QF_GETLIST_ID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("id"), 0);
}
- if (tv_dict_find(what, S_LEN("size")) != NULL) {
- flags |= QF_GETLIST_SIZE;
+ if ((status == OK) && (flags & QF_GETLIST_IDX)) {
+ status = tv_dict_add_nr(retdict, S_LEN("idx"), 0);
+ }
+ if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
+ status = tv_dict_add_nr(retdict, S_LEN("size"), 0);
+ }
+ if ((status == OK) && (flags & QF_GETLIST_TICK)) {
+ status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0);
}
+ return status;
+}
- if (flags & QF_GETLIST_TITLE) {
+/// Return the quickfix list title as 'title' in retdict
+static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
char_u *t = qi->qf_lists[qf_idx].qf_title;
if (t == NULL) {
t = (char_u *)"";
}
- status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)t);
+ return tv_dict_add_str(retdict, S_LEN("title"), (const char *)t);
+}
+
+/// Return the quickfix list items/entries as 'items' in retdict
+static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
+ list_T *l = tv_list_alloc(kListLenMayKnow);
+ get_errorlist(qi, NULL, qf_idx, l);
+ tv_dict_add_list(retdict, S_LEN("items"), l);
+
+ return OK;
+}
+
+/// Return the quickfix list context (if any) as 'context' in retdict.
+static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
+ int status;
+
+ if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
+ dictitem_T *di = tv_dict_item_alloc_len(S_LEN("context"));
+ tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
+ status = tv_dict_add(retdict, di);
+ if (status == FAIL) {
+ tv_dict_item_free(di);
+ }
+ } else {
+ status = tv_dict_add_str(retdict, S_LEN("context"), "");
+ }
+
+ return status;
+}
+
+/// Return the quickfix list index as 'idx' in retdict
+static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
+ int idx = qi->qf_lists[qf_idx].qf_index;
+ if (qi->qf_lists[qf_idx].qf_count == 0) {
+ // For empty lists, qf_index is set to 1
+ idx = 0;
+ }
+ return tv_dict_add_nr(retdict, S_LEN("idx"), idx);
+}
+
+/// 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.
+int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
+{
+ qf_info_T *qi = &ql_info;
+ dictitem_T *di = NULL;
+ int status = OK;
+ int qf_idx;
+
+ 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);
+ }
+
+ int flags = qf_getprop_keys2flags(what);
+
+ if (qi != NULL && qi->qf_listcount != 0) {
+ qf_idx = qf_getprop_qfidx(qi, what);
+ }
+
+ // List is not present or is empty
+ if (qi == NULL || qi->qf_listcount == 0 || qf_idx == -1) {
+ return qf_getprop_defaults(qi, flags, retdict);
+ }
+
+ if (flags & QF_GETLIST_TITLE) {
+ status = qf_getprop_title(qi, qf_idx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_NR)) {
status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1);
}
if ((status == OK) && (flags & QF_GETLIST_WINID)) {
- win_T *win = qf_find_win(qi);
- if (win != NULL) {
- status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle);
- }
+ status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
- list_T *l = tv_list_alloc(kListLenMayKnow);
- (void)get_errorlist(qi, NULL, qf_idx, l);
- tv_dict_add_list(retdict, S_LEN("items"), l);
+ status = qf_getprop_items(qi, qf_idx, retdict);
}
-
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
- if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
- di = tv_dict_item_alloc_len(S_LEN("context"));
- tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
- status = tv_dict_add(retdict, di);
- if (status == FAIL) {
- tv_dict_item_free(di);
- }
- } else {
- status = tv_dict_add_str(retdict, S_LEN("context"), "");
- }
+ status = qf_getprop_ctx(qi, qf_idx, retdict);
}
-
if ((status == OK) && (flags & QF_GETLIST_ID)) {
status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id);
}
-
if ((status == OK) && (flags & QF_GETLIST_IDX)) {
- int idx = qi->qf_lists[qf_idx].qf_index;
- if (qi->qf_lists[qf_idx].qf_count == 0) {
- // For empty lists, qf_index is set to 1
- idx = 0;
- }
- status = tv_dict_add_nr(retdict, S_LEN("idx"), idx);
+ status = qf_getprop_idx(qi, qf_idx, retdict);
}
-
if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
status = tv_dict_add_nr(retdict, S_LEN("size"),
qi->qf_lists[qf_idx].qf_count);
}
+ if ((status == OK) && (flags & QF_GETLIST_TICK)) {
+ status = tv_dict_add_nr(retdict, S_LEN("changedtick"),
+ qi->qf_lists[qf_idx].qf_changedtick);
+ }
return status;
}
@@ -4434,6 +4837,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
}
char *const filename = tv_dict_get_string(d, "filename", true);
+ char *const module = tv_dict_get_string(d, "module", true);
int bufnum = (int)tv_dict_get_number(d, "bufnr");
long lnum = (long)tv_dict_get_number(d, "lnum");
int col = (int)tv_dict_get_number(d, "col");
@@ -4471,6 +4875,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
qf_idx,
NULL, // dir
(char_u *)filename,
+ (char_u *)module,
bufnum,
(char_u *)text,
lnum,
@@ -4482,6 +4887,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
valid);
xfree(filename);
+ xfree(module);
xfree(pattern);
xfree(text);
@@ -4633,6 +5039,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action,
retval = OK;
}
+ if (retval == OK) {
+ qf_list_changed(qi, qf_idx);
+ }
+
return retval;
}
@@ -4714,11 +5124,15 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
retval = qf_set_properties(qi, what, action, title);
} else {
retval = qf_add_entries(qi, qi->qf_curlist, list, title, action);
+ if (retval == OK) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
}
return retval;
}
+/// Mark the context as in use for all the lists in a quickfix stack.
static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
{
bool abort = false;
@@ -4735,7 +5149,7 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
}
/// Mark the context of the quickfix list and the location lists (if present) as
-/// "in use". So that garabage collection doesn't free the context.
+/// "in use". So that garbage collection doesn't free the context.
bool set_ref_in_quickfix(int copyID)
{
bool abort = mark_quickfix_ctx(&ql_info, copyID);
@@ -4820,10 +5234,10 @@ void ex_cbuffer(exarg_T *eap)
eap->line2 = buf->b_ml.ml_line_count;
}
if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count
- || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count)
+ || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) {
EMSG(_(e_invrange));
- else {
- char_u *qf_title = *eap->cmdlinep;
+ } else {
+ char_u *qf_title = qf_cmdtitle(*eap->cmdlinep);
if (buf->b_sfname) {
vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)",
@@ -4831,17 +5245,20 @@ void ex_cbuffer(exarg_T *eap)
qf_title = IObuff;
}
- 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) {
- if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
- curbuf->b_fname, true, curbuf);
- }
- if (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) {
- qf_jump(qi, 0, 0, eap->forceit); // display first error
- }
+ int res = 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);
+ if (res >= 0) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
+ if (au_name != NULL) {
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
+ curbuf->b_fname, true, curbuf);
+ }
+ if (res > 0 && (eap->cmdidx == CMD_cbuffer
+ || eap->cmdidx == CMD_lbuffer)) {
+ qf_jump(qi, 0, 0, eap->forceit); // display first error
}
}
}
@@ -4856,11 +5273,6 @@ void ex_cexpr(exarg_T *eap)
qf_info_T *qi = &ql_info;
const char *au_name = NULL;
- if (eap->cmdidx == CMD_lexpr || eap->cmdidx == CMD_lgetexpr
- || eap->cmdidx == CMD_laddexpr) {
- qi = ll_get_or_alloc_list(curwin);
- }
-
switch (eap->cmdidx) {
case CMD_cexpr:
au_name = "cexpr";
@@ -4890,23 +5302,32 @@ void ex_cexpr(exarg_T *eap)
}
}
+ if (eap->cmdidx == CMD_lexpr
+ || eap->cmdidx == CMD_lgetexpr
+ || eap->cmdidx == CMD_laddexpr) {
+ qi = ll_get_or_alloc_list(curwin);
+ }
+
/* Evaluate the expression. When the result is a string or a list we can
* use it to fill the errorlist. */
typval_T tv;
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, 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) {
- if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
- curbuf->b_fname, true, curbuf);
- }
- if (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) {
- qf_jump(qi, 0, 0, eap->forceit); // display first error
- }
+ int res = 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,
+ qf_cmdtitle(*eap->cmdlinep), NULL);
+ if (res >= 0) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
+ if (au_name != NULL) {
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
+ curbuf->b_fname, true, curbuf);
+ }
+ if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) {
+ qf_jump(qi, 0, 0, eap->forceit); // display first error
}
} else {
EMSG(_("E777: String or List expected"));
@@ -4980,16 +5401,13 @@ void ex_helpgrep(exarg_T *eap)
}
}
- // Autocommands may change the list. Save it for later comparison
- qf_info_T *save_qi = qi;
-
regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING);
regmatch.rm_ic = FALSE;
if (regmatch.regprog != NULL) {
// Create a new quickfix list.
- qf_new_list(qi, *eap->cmdlinep);
+ qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep));
- /* Go through all directories in 'runtimepath' */
+ // Go through all the directories in 'runtimepath'
p = p_rtp;
while (*p != NUL && !got_int) {
copy_option_part(&p, NameBuff, MAXPATHL, ",");
@@ -5004,15 +5422,15 @@ void ex_helpgrep(exarg_T *eap)
if (gen_expand_wildcards(1, buff_list, &fcount,
&fnames, EW_FILE|EW_SILENT) == OK
&& fcount > 0) {
- for (fi = 0; fi < fcount && !got_int; ++fi) {
- /* Skip files for a different language. */
+ for (fi = 0; fi < fcount && !got_int; fi++) {
+ // Skip files for a different language.
if (lang != NULL
- && STRNICMP(lang, fnames[fi]
- + STRLEN(fnames[fi]) - 3, 2) != 0
+ && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0
&& !(STRNICMP(lang, "en", 2) == 0
&& STRNICMP("txt", fnames[fi]
- + STRLEN(fnames[fi]) - 3, 3) == 0))
+ + STRLEN(fnames[fi]) - 3, 3) == 0)) {
continue;
+ }
fd = mch_fopen((char *)fnames[fi], "r");
if (fd != NULL) {
lnum = 1;
@@ -5029,6 +5447,7 @@ void ex_helpgrep(exarg_T *eap)
qi->qf_curlist,
NULL, // dir
fnames[fi],
+ NULL,
0,
line,
lnum,
@@ -5073,12 +5492,13 @@ void ex_helpgrep(exarg_T *eap)
/* Darn, some plugin changed the value. */
free_string_option(save_cpo);
+ qf_list_changed(qi, qi->qf_curlist);
qf_update_buffer(qi, NULL);
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, true, curbuf);
- if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) {
+ if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) {
// autocommands made "qi" invalid
return;
}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index c602e7df0f..39ce7ff844 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -5098,8 +5098,6 @@ static int regmatch(
printf("Premature EOL\n");
#endif
}
- if (status == RA_FAIL)
- got_int = TRUE;
return status == RA_MATCH;
}
@@ -7226,7 +7224,8 @@ static void report_re_switch(char_u *pat)
/// @param nl
///
/// @return TRUE if there is a match, FALSE if not.
-static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl)
+static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
+ bool nl)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
@@ -7275,8 +7274,8 @@ static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl)
int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
colnr_T col)
{
- regmatch_T regmatch = {.regprog = *prog, .rm_ic = ignore_case};
- int r = vim_regexec_both(&regmatch, line, col, false);
+ regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case };
+ int r = vim_regexec_string(&regmatch, line, col, false);
*prog = regmatch.regprog;
return r;
}
@@ -7285,7 +7284,7 @@ int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
// Return TRUE if there is a match, FALSE if not.
int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
{
- return vim_regexec_both(rmp, line, col, false);
+ return vim_regexec_string(rmp, line, col, false);
}
// Like vim_regexec(), but consider a "\n" in "line" to be a line break.
@@ -7293,7 +7292,7 @@ int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
// Return TRUE if there is a match, FALSE if not.
int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
{
- return vim_regexec_both(rmp, line, col, true);
+ return vim_regexec_string(rmp, line, col, true);
}
/// Match a regexp against multiple lines.
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 298d82fc6b..5e5b19b63f 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -94,10 +94,8 @@ typedef struct {
char_u program[1]; /* actually longer.. */
} bt_regprog_T;
-/*
- * Structure representing a NFA state.
- * A NFA state may have no outgoing edge, when it is a NFA_MATCH state.
- */
+// Structure representing a NFA state.
+// An NFA state may have no outgoing edge, when it is a NFA_MATCH state.
typedef struct nfa_state nfa_state_T;
struct nfa_state {
int c;
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index b935b44291..ce7270ae65 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -4968,13 +4968,15 @@ static int nfa_did_time_out(void)
///
/// When "nfa_endp" is not NULL it is a required end-of-match position.
///
-/// Return TRUE if there is a match, FALSE otherwise.
+/// Return TRUE if there is a match, FALSE if there is no match,
+/// NFA_TOO_EXPENSIVE if we end up with too many states.
/// When there is a match "submatch" contains the positions.
+///
/// Note: Caller must ensure that: start != NULL.
static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
regsubs_T *submatch, regsubs_T *m)
{
- int result;
+ int result = false;
int flag = 0;
bool go_to_nextline = false;
nfa_thread_T *t;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index b4ebf2ece5..2709ba83f3 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -678,8 +678,6 @@ static void win_update(win_T *wp)
mod_bot = wp->w_redraw_bot + 1;
else
mod_bot = 0;
- wp->w_redraw_top = 0; /* reset for next time */
- wp->w_redraw_bot = 0;
if (buf->b_mod_set) {
if (mod_top == 0 || mod_top > buf->b_mod_top) {
mod_top = buf->b_mod_top;
@@ -776,6 +774,8 @@ static void win_update(win_T *wp)
if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu)
mod_bot = MAXLNUM;
}
+ wp->w_redraw_top = 0; // reset for next time
+ wp->w_redraw_bot = 0;
/*
* When only displaying the lines at the top, set top_end. Used when
@@ -1135,6 +1135,9 @@ static void win_update(win_T *wp)
/* reset got_int, otherwise regexp won't work */
save_got_int = got_int;
got_int = 0;
+ // Set the time limit to 'redrawtime'.
+ proftime_T syntax_tm = profile_setlimit(p_rdt);
+ syn_set_timeout(&syntax_tm);
win_foldinfo.fi_level = 0;
/*
@@ -1493,6 +1496,7 @@ static void win_update(win_T *wp)
if (wp->w_redr_type >= REDRAW_TOP) {
draw_vsep_win(wp, 0);
}
+ syn_set_timeout(NULL);
/* Reset the type of redrawing required, the window has been updated. */
wp->w_redr_type = 0;
@@ -2189,7 +2193,7 @@ win_line (
// To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done.
extra_check = wp->w_p_lbr;
- if (syntax_present(wp) && !wp->w_s->b_syn_error) {
+ if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) {
// Prepare for syntax highlighting in this line. When there is an
// error, stop syntax highlighting.
save_did_emsg = did_emsg;
@@ -2199,8 +2203,10 @@ win_line (
wp->w_s->b_syn_error = true;
} else {
did_emsg = save_did_emsg;
- has_syntax = true;
- extra_check = true;
+ if (!wp->w_s->b_syn_slow) {
+ has_syntax = true;
+ extra_check = true;
+ }
}
}
@@ -2440,7 +2446,9 @@ win_line (
}
if (wp->w_p_list) {
- if (curwin->w_p_lcs_chars.space || wp->w_p_lcs_chars.trail) {
+ if (curwin->w_p_lcs_chars.space
+ || wp->w_p_lcs_chars.trail
+ || wp->w_p_lcs_chars.nbsp) {
extra_check = true;
}
// find start of trailing whitespace
@@ -2539,9 +2547,10 @@ win_line (
}
wp->w_cursor = pos;
- /* Need to restart syntax highlighting for this line. */
- if (has_syntax)
+ // Need to restart syntax highlighting for this line.
+ if (has_syntax) {
syntax_start(wp, lnum);
+ }
}
}
@@ -2587,6 +2596,9 @@ win_line (
}
next_search_hl(wp, shl, lnum, (colnr_T)v,
shl == &search_hl ? NULL : cur);
+ if (wp->w_s->b_syn_slow) {
+ has_syntax = false;
+ }
// Need to get the line again, a multi-line regexp may have made it
// invalid.
@@ -3265,16 +3277,18 @@ win_line (
line = ml_get_buf(wp->w_buffer, lnum, FALSE);
ptr = line + v;
- if (!attr_pri)
+ if (!attr_pri) {
char_attr = syntax_attr;
- else
+ } else {
char_attr = hl_combine_attr(syntax_attr, char_attr);
- /* no concealing past the end of the line, it interferes
- * with line highlighting */
- if (c == NUL)
+ }
+ // no concealing past the end of the line, it interferes
+ // with line highlighting.
+ if (c == NUL) {
syntax_flags = 0;
- else
+ } else {
syntax_flags = get_syntax_info(&syntax_seqnr);
+ }
} else if (!attr_pri) {
char_attr = 0;
}
@@ -3366,7 +3380,7 @@ win_line (
}
if (wp->w_buffer->terminal) {
- char_attr = hl_combine_attr(char_attr, term_attrs[vcol]);
+ char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
}
// Found last space before word: check for line break.
@@ -3981,8 +3995,10 @@ win_line (
break;
}
- // line continues beyond line end
- if (wp->w_p_lcs_chars.ext
+ // Show "extends" character from 'listchars' if beyond the line end and
+ // 'list' is set.
+ if (wp->w_p_lcs_chars.ext != NUL
+ && wp->w_p_list
&& !wp->w_p_wrap
&& filler_todo <= 0
&& (wp->w_p_rl ? col == 0 : col == grid->Columns - 1)
@@ -4821,13 +4837,13 @@ static void win_redr_status(win_T *wp)
p = NameBuff;
len = (int)STRLEN(p);
- if (wp->w_buffer->b_help
+ if (bt_help(wp->w_buffer)
|| wp->w_p_pvw
|| bufIsChanged(wp->w_buffer)
|| wp->w_buffer->b_p_ro) {
*(p + len++) = ' ';
}
- if (wp->w_buffer->b_help) {
+ if (bt_help(wp->w_buffer)) {
STRCPY(p + len, _("[Help]"));
len += (int)STRLEN(p + len);
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 6bbf91e0f9..eace4a524c 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1542,7 +1542,7 @@ static char *shada_filename(const char *file)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
if (file == NULL || *file == NUL) {
- if (p_shadafile != NULL) {
+ if (p_shadafile != NULL && *p_shadafile != NUL) {
file = p_shadafile;
} else {
if ((file = find_shada_parameter('n')) == NULL || *file == NUL) {
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 331d8da55a..0fc33bec81 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -7100,9 +7100,9 @@ void ex_spelldump(exarg_T *eap)
spell_dump_compl(NULL, 0, NULL, eap->forceit ? DUMPFLAG_COUNT : 0);
// Delete the empty line that we started with.
- if (curbuf->b_ml.ml_line_count > 1)
- ml_delete(curbuf->b_ml.ml_line_count, FALSE);
-
+ if (curbuf->b_ml.ml_line_count > 1) {
+ ml_delete(curbuf->b_ml.ml_line_count, false);
+ }
redraw_later(NOT_VALID);
}
@@ -7171,7 +7171,7 @@ spell_dump_compl (
if (do_region && region_names != NULL) {
if (pat == NULL) {
vim_snprintf((char *)IObuff, IOSIZE, "/regions=%s", region_names);
- ml_append(lnum++, IObuff, (colnr_T)0, FALSE);
+ ml_append(lnum++, IObuff, (colnr_T)0, false);
}
} else
do_region = false;
@@ -7185,7 +7185,7 @@ spell_dump_compl (
if (pat == NULL) {
vim_snprintf((char *)IObuff, IOSIZE, "# file: %s", slang->sl_fname);
- ml_append(lnum++, IObuff, (colnr_T)0, FALSE);
+ ml_append(lnum++, IObuff, (colnr_T)0, false);
}
// When matching with a pattern and there are no prefixes only use
@@ -7347,14 +7347,15 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, int *dir, int d
}
}
- ml_append(lnum, p, (colnr_T)0, FALSE);
+ ml_append(lnum, p, (colnr_T)0, false);
} else if (((dumpflags & DUMPFLAG_ICASE)
? mb_strnicmp(p, pat, STRLEN(pat)) == 0
: STRNCMP(p, pat, STRLEN(pat)) == 0)
&& ins_compl_add_infercase(p, (int)STRLEN(p),
- p_ic, NULL, *dir, 0) == OK)
+ p_ic, NULL, *dir, 0) == OK) {
// if dir was BACKWARD then honor it just once
*dir = FORWARD;
+ }
}
// For ":spelldump": Find matching prefixes for "word". Prepend each to
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index ccad893d61..117939e7e9 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -955,8 +955,9 @@ someerror:
break;
}
if (ml_append_buf(slang->sl_sugbuf, (linenr_T)wordnr,
- ga.ga_data, ga.ga_len, TRUE) == FAIL)
+ ga.ga_data, ga.ga_len, true) == FAIL) {
goto someerror;
+ }
}
ga_clear(&ga);
@@ -4920,9 +4921,10 @@ sug_filltable (
((char_u *)gap->ga_data)[gap->ga_len++] = NUL;
if (ml_append_buf(spin->si_spellbuf, (linenr_T)wordnr,
- gap->ga_data, gap->ga_len, TRUE) == FAIL)
+ gap->ga_data, gap->ga_len, true) == FAIL) {
return -1;
- ++wordnr;
+ }
+ wordnr++;
// Remove extra NUL entries, we no longer need them. We don't
// bother freeing the nodes, the won't be reused anyway.
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 8c3ce823d3..1b30161e94 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -356,15 +356,16 @@ static reg_extmatch_T *next_match_extmatch = NULL;
* The current state (within the line) of the recognition engine.
* When current_state.ga_itemsize is 0 the current state is invalid.
*/
-static win_T *syn_win; /* current window for highlighting */
-static buf_T *syn_buf; /* current buffer for highlighting */
-static synblock_T *syn_block; /* current buffer for highlighting */
-static linenr_T current_lnum = 0; /* lnum of current state */
-static colnr_T current_col = 0; /* column of current state */
-static int current_state_stored = 0; /* TRUE if stored current state
- * after setting current_finished */
-static int current_finished = 0; /* current line has been finished */
-static garray_T current_state /* current stack of state_items */
+static win_T *syn_win; // current window for highlighting
+static buf_T *syn_buf; // current buffer for highlighting
+static synblock_T *syn_block; // current buffer for highlighting
+static proftime_T *syn_tm; // timeout limit
+static linenr_T current_lnum = 0; // lnum of current state
+static colnr_T current_col = 0; // column of current state
+static int current_state_stored = 0; // TRUE if stored current state
+ // after setting current_finished
+static int current_finished = 0; // current line has been finished
+static garray_T current_state // current stack of state_items
= GA_EMPTY_INIT_VALUE;
static int16_t *current_next_list = NULL; // when non-zero, nextgroup list
static int current_next_flags = 0; // flags for current_next_list
@@ -375,7 +376,12 @@ static int current_line_id = 0; // unique number for current line
static int syn_time_on = FALSE;
# define IF_SYN_TIME(p) (p)
-
+// Set the timeout used for syntax highlighting.
+// Use NULL to reset, no timeout.
+void syn_set_timeout(proftime_T *tm)
+{
+ syn_tm = tm;
+}
/*
* Start the syntax recognition for a line. This function is normally called
@@ -2887,6 +2893,7 @@ static char_u *syn_getcurline(void)
static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st)
{
int r;
+ int timed_out = 0;
proftime_T pt;
const int l_syn_time_on = syn_time_on;
@@ -2902,7 +2909,8 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
}
rmp->rmm_maxcol = syn_buf->b_p_smc;
- r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, NULL, NULL);
+ r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
+ syn_tm, &timed_out);
if (l_syn_time_on) {
pt = profile_end(pt);
@@ -2914,6 +2922,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
if (r > 0)
++st->match;
}
+ if (timed_out) {
+ syn_win->w_s->b_syn_slow = true;
+ }
if (r > 0) {
rmp->startpos[0].lnum += lnum;
@@ -3144,6 +3155,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
void syntax_clear(synblock_T *block)
{
block->b_syn_error = false; // clear previous error
+ block->b_syn_slow = false; // clear previous timeout
block->b_syn_ic = false; // Use case, by default
block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
block->b_syn_containedin = false;
@@ -5756,8 +5768,10 @@ int syn_get_foldlevel(win_T *wp, long lnum)
{
int level = 0;
- /* Return quickly when there are no fold items at all. */
- if (wp->w_s->b_syn_folditems != 0) {
+ // Return quickly when there are no fold items at all.
+ if (wp->w_s->b_syn_folditems != 0
+ && !wp->w_s->b_syn_error
+ && !wp->w_s->b_syn_slow) {
syntax_start(wp, lnum);
for (int i = 0; i < current_state.ga_len; ++i) {
@@ -7446,7 +7460,7 @@ void highlight_attr_set_all(void)
void highlight_changed(void)
{
int id;
- char_u userhl[10];
+ char_u userhl[30]; // use 30 to avoid compiler warning
int id_SNC = -1;
int id_S = -1;
int hlcnt;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 861063ff62..81af23f911 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1832,9 +1832,9 @@ parse_line:
// Don't add identical matches.
// Add all cscope tags, because they are all listed.
// "mfp" is used as a hash key, there is a NUL byte to end
- // the part matters for comparing, more bytes may follow
- // after it. E.g. help tags store the priority after the
- // NUL.
+ // the part that matters for comparing, more bytes may
+ // follow after it. E.g. help tags store the priority
+ // after the NUL.
if (use_cscope) {
hash++;
} else {
@@ -1970,7 +1970,13 @@ static garray_T tag_fnames = GA_EMPTY_INIT_VALUE;
*/
static void found_tagfile_cb(char_u *fname, void *cookie)
{
- GA_APPEND(char_u *, &tag_fnames, vim_strsave(fname));
+ char_u *const tag_fname = vim_strsave(fname);
+
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(tag_fname);
+#endif
+ simplify_filename(tag_fname);
+ GA_APPEND(char_u *, &tag_fnames, tag_fname);
}
#if defined(EXITFREE)
@@ -2028,9 +2034,20 @@ get_tagfname (
++tnp->tn_hf_idx;
STRCPY(buf, p_hf);
STRCPY(path_tail(buf), "tags");
- } else
- STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[
- tnp->tn_hf_idx++], MAXPATHL);
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(buf);
+#endif
+ simplify_filename(buf);
+
+ for (int i = 0; i < tag_fnames.ga_len; i++) {
+ if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0) {
+ return FAIL; // avoid duplicate file names
+ }
+ }
+ } else {
+ STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[tnp->tn_hf_idx++],
+ MAXPATHL);
+ }
return OK;
}
@@ -2863,3 +2880,177 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
}
return ret;
}
+
+// Return information about 'tag' in dict 'retdict'.
+static void get_tag_details(taggy_T *tag, dict_T *retdict)
+{
+ list_T *pos;
+ fmark_T *fmark;
+
+ tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname);
+ tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1);
+ tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum);
+
+ pos = tv_list_alloc(4);
+ tv_dict_add_list(retdict, S_LEN("from"), pos);
+
+ fmark = &tag->fmark;
+ tv_list_append_number(pos,
+ (varnumber_T)(fmark->fnum != -1 ? fmark->fnum : 0));
+ tv_list_append_number(pos, (varnumber_T)fmark->mark.lnum);
+ tv_list_append_number(pos, (varnumber_T)(fmark->mark.col == MAXCOL
+ ? MAXCOL : fmark->mark.col + 1));
+ tv_list_append_number(pos, (varnumber_T)fmark->mark.coladd);
+}
+
+// Return the tag stack entries of the specified window 'wp' in dictionary
+// 'retdict'.
+void get_tagstack(win_T *wp, dict_T *retdict)
+{
+ list_T *l;
+ int i;
+ dict_T *d;
+
+ tv_dict_add_nr(retdict, S_LEN("length"), wp->w_tagstacklen);
+ tv_dict_add_nr(retdict, S_LEN("curidx"), wp->w_tagstackidx + 1);
+ l = tv_list_alloc(2);
+ tv_dict_add_list(retdict, S_LEN("items"), l);
+
+ for (i = 0; i < wp->w_tagstacklen; i++) {
+ d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ get_tag_details(&wp->w_tagstack[i], d);
+ }
+}
+
+// Free all the entries in the tag stack of the specified window
+static void tagstack_clear(win_T *wp)
+{
+ // Free the current tag stack
+ for (int i = 0; i < wp->w_tagstacklen; i++) {
+ xfree(wp->w_tagstack[i].tagname);
+ }
+ wp->w_tagstacklen = 0;
+ wp->w_tagstackidx = 0;
+}
+
+// Remove the oldest entry from the tag stack and shift the rest of
+// the entires to free up the top of the stack.
+static void tagstack_shift(win_T *wp)
+{
+ taggy_T *tagstack = wp->w_tagstack;
+ xfree(tagstack[0].tagname);
+ for (int i = 1; i < wp->w_tagstacklen; i++) {
+ tagstack[i - 1] = tagstack[i];
+ }
+ wp->w_tagstacklen--;
+}
+
+// Push a new item to the tag stack
+static void tagstack_push_item(
+ win_T *wp,
+ char_u *tagname,
+ int cur_fnum,
+ int cur_match,
+ pos_T mark,
+ int fnum)
+{
+ taggy_T *tagstack = wp->w_tagstack;
+ int idx = wp->w_tagstacklen; // top of the stack
+
+ // if the tagstack is full: remove the oldest entry
+ if (idx >= TAGSTACKSIZE) {
+ tagstack_shift(wp);
+ idx = TAGSTACKSIZE - 1;
+ }
+
+ wp->w_tagstacklen++;
+ tagstack[idx].tagname = tagname;
+ tagstack[idx].cur_fnum = cur_fnum;
+ tagstack[idx].cur_match = cur_match;
+ if (tagstack[idx].cur_match < 0) {
+ tagstack[idx].cur_match = 0;
+ }
+ tagstack[idx].fmark.mark = mark;
+ tagstack[idx].fmark.fnum = fnum;
+}
+
+// Add a list of items to the tag stack in the specified window
+static void tagstack_push_items(win_T *wp, list_T *l)
+{
+ listitem_T *li;
+ dictitem_T *di;
+ dict_T *itemdict;
+ char_u *tagname;
+ pos_T mark;
+ int fnum;
+
+ // Add one entry at a time to the tag stack
+ for (li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
+ || TV_LIST_ITEM_TV(li)->vval.v_dict == NULL) {
+ continue; // Skip non-dict items
+ }
+ itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict;
+
+ // parse 'from' for the cursor position before the tag jump
+ if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
+ continue;
+ }
+ if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) {
+ continue;
+ }
+ if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true))
+ == NULL) {
+ continue;
+ }
+
+ if (mark.col > 0) {
+ mark.col--;
+ }
+ tagstack_push_item(wp, tagname,
+ (int)tv_dict_get_number(itemdict, "bufnr"),
+ (int)tv_dict_get_number(itemdict, "matchnr") - 1,
+ mark, fnum);
+ }
+}
+
+// Set the current index in the tag stack. Valid values are between 0
+// and the stack length (inclusive).
+static void tagstack_set_curidx(win_T *wp, int curidx)
+{
+ wp->w_tagstackidx = curidx;
+ if (wp->w_tagstackidx < 0) { // sanity check
+ wp->w_tagstackidx = 0;
+ }
+ if (wp->w_tagstackidx > wp->w_tagstacklen) {
+ wp->w_tagstackidx = wp->w_tagstacklen;
+ }
+}
+
+// Set the tag stack entries of the specified window.
+// 'action' is set to either 'a' for append or 'r' for replace.
+int set_tagstack(win_T *wp, dict_T *d, int action)
+{
+ dictitem_T *di;
+ list_T *l;
+
+ if ((di = tv_dict_find(d, "items", -1)) != NULL) {
+ if (di->di_tv.v_type != VAR_LIST) {
+ return FAIL;
+ }
+ l = di->di_tv.vval.v_list;
+
+ if (action == 'r') {
+ tagstack_clear(wp);
+ }
+
+ tagstack_push_items(wp, l);
+ }
+
+ if ((di = tv_dict_find(d, "curidx", -1)) != NULL) {
+ tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
+ }
+
+ return OK;
+}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index bdcade0d86..d8d529d0f6 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -374,6 +374,7 @@ void terminal_enter(void)
TerminalState state, *s = &state;
memset(s, 0, sizeof(TerminalState));
s->term = buf->terminal;
+ stop_insert_mode = false;
// Ensure the terminal is properly sized. Ideally window size management
// code should always have resized the terminal already, but check here to
@@ -450,7 +451,6 @@ static void terminal_check_cursor(void)
static int terminal_check(VimState *state)
{
if (stop_insert_mode) {
- stop_insert_mode = false;
return 0;
}
@@ -1339,23 +1339,16 @@ static bool is_focused(Terminal *term)
return State & TERM_FOCUS && curbuf->terminal == term;
}
-#define GET_CONFIG_VALUE(k, o) \
- do { \
- Error err = ERROR_INIT; \
- /* Only called from terminal_open where curbuf->terminal is the */ \
- /* context */ \
- o = dict_get_value(curbuf->b_vars, cstr_as_string(k), &err); \
- api_clear_error(&err); \
- if (o.type == kObjectTypeNil) { \
- o = dict_get_value(&globvardict, cstr_as_string(k), &err); \
- api_clear_error(&err); \
- } \
- } while (0)
-
static char *get_config_string(char *key)
{
- Object obj;
- GET_CONFIG_VALUE(key, obj);
+ Error err = ERROR_INIT;
+ // Only called from terminal_open where curbuf->terminal is the context.
+ Object obj = dict_get_value(curbuf->b_vars, cstr_as_string(key), &err);
+ api_clear_error(&err);
+ if (obj.type == kObjectTypeNil) {
+ obj = dict_get_value(&globvardict, cstr_as_string(key), &err);
+ api_clear_error(&err);
+ }
if (obj.type == kObjectTypeString) {
return obj.data.string.data;
}
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index fac27aa346..8ea2689939 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -14,7 +14,6 @@ export NVIM_PRG := $(NVIM_PRG)
export TMPDIR := $(abspath ../../../Xtest-tmpdir)
SCRIPTS_DEFAULT = \
- test14.out \
test37.out \
test42.out \
test48.out \
@@ -23,7 +22,6 @@ SCRIPTS_DEFAULT = \
ifneq ($(OS),Windows_NT)
SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \
- test17.out \
test49.out \
endif
@@ -50,8 +48,6 @@ NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \
NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT))
-SCRIPTS_GUI := test16.out
-
ifdef VALGRIND_GDB
VGDB := --vgdb=yes \
@@ -83,8 +79,6 @@ endif
nongui: nolog $(SCRIPTS) newtests report
-gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) newtests report
-
.gdbinit:
@echo "[OLDTEST-PREP] Setting up .gdbinit"
@echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit
@@ -102,7 +96,7 @@ report:
test1.out: $(NVIM_PRG)
-$(SCRIPTS) $(SCRIPTS_GUI): $(NVIM_PRG) test1.out
+$(SCRIPTS): $(NVIM_PRG) test1.out
RM_ON_RUN := test.out X* viminfo
RM_ON_START := test.ok
@@ -164,5 +158,6 @@ newtestssilent: $(NEW_TESTS)
%.res: %.vim .gdbinit
@echo "[OLDTEST] Running" $*
+ @rm -rf $*.failed test.ok $(RM_ON_RUN)
@mkdir -p $(TMPDIR)
@/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim
diff --git a/src/nvim/testdir/test14.in b/src/nvim/testdir/test14.in
deleted file mode 100644
index bef2e45431..0000000000
--- a/src/nvim/testdir/test14.in
+++ /dev/null
@@ -1,94 +0,0 @@
-Tests for "vaBiB", end could be wrong.
-Also test ":s/pat/sub/" with different ~s in sub.
-Also test for ^Vxff and ^Vo123 in Insert mode.
-Also test "[m", "]m", "[M" and "]M"
-Also test search()
-
-STARTTEST
-/Start cursor here
-vaBiBD:?Bug?,/Piece/-2w! test.out
-/^- Bug
-:s/u/~u~/
-:s/i/~u~/
-:s/o/~~~/
-:.w >>test.out
-:let tt = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
-:exe "normal " . tt
-:unlet tt
-:.w >>test.out
-:set vb
-/^Piece
-2]maA:.w >>test.out
-j]maB:.w >>test.out
-]maC:.w >>test.out
-[maD:.w >>test.out
-k2[maE:.w >>test.out
-3[maF:.w >>test.out
-]MaG:.w >>test.out
-j2]MaH:.w >>test.out
-]M]MaI:.w >>test.out
-2[MaJ:.w >>test.out
-k[MaK:.w >>test.out
-3[MaL:.w >>test.out
-:"
-/^foobar
-:let startline = line('.')
-:call search('foobar', 'c')
-:call append(line('$'), line('.') - startline)
-j:call search('^$', 'c')
-:call append(line('$'), line('.') - startline)
-:call search('^$', 'bc')
-:call append(line('$'), line('.') - startline)
-/two
-:call search('.', 'c')
-:call append(line('$'), getline('.')[col('.') - 1:])
-:"
-/^substitute
-:s/foo/bar/
-:$put =@/
-/^substitute
-:keeppatterns s/asdf/xyz/
-:$put =@/
-/^substitute
-Y:$put =@0
-/bar /e
-:$put =@0
--:keeppatterns /xyz
-0dn:/^search()/,$w >>test.out
-:qa!
-ENDTEST
-
-- Bug in "vPPPP" on this text (Webb):
- {
- cmd;
- {
- cmd; /* <-- Start cursor here */
- {
- }
- }
- }
-
-Piece of Java
-{
- tt m1 {
- t1;
- } e1
-
- tt m2 {
- t2;
- } e2
-
- tt m3 {
- if (x)
- {
- t3;
- }
- } e3
-}
-
-foobar
-
-substitute foo asdf
-
-one two
-search()
diff --git a/src/nvim/testdir/test14.ok b/src/nvim/testdir/test14.ok
deleted file mode 100644
index 0aa2db3f97..0000000000
--- a/src/nvim/testdir/test14.ok
+++ /dev/null
@@ -1,26 +0,0 @@
-- Bug in "vPPPP" on this text (Webb):
- {
- }
-- Bug uuun "vPPPP" uuuuuuuuun this text (Webb):
-ABC !ag8
- tt m1 {A
- tt m2 {B
- tt m3 {C
- tt m3 {DC
- tt m1 {EA
-{F
- }G e1
- }H e3
-}I
- }JH e3
- }K e2
-{LF
-search()
-0
-1
-1
-two
-foo
-^substitute
-substitute bar xyz
-xyz
diff --git a/src/nvim/testdir/test16.in b/src/nvim/testdir/test16.in
deleted file mode 100644
index b2cd159a8c..0000000000
--- a/src/nvim/testdir/test16.in
+++ /dev/null
@@ -1,15 +0,0 @@
-Tests for resetting "secure" flag after GUI has started.
-For KDE set a font, empty 'guifont' may cause a hang.
-
-STARTTEST
-:if $DISPLAY == "" | e! test.ok | wq! test.out | endif
-:set exrc secure
-:if has("gui_kde")
-: set guifont=Courier\ 10\ Pitch/8/-1/5/50/0/0/0/0/0
-:endif
-:gui -f
-:.,$w! test.out
-:qa!
-ENDTEST
-
- just some text
diff --git a/src/nvim/testdir/test16.ok b/src/nvim/testdir/test16.ok
deleted file mode 100644
index 25e2eea5c0..0000000000
--- a/src/nvim/testdir/test16.ok
+++ /dev/null
@@ -1,2 +0,0 @@
-
- just some text
diff --git a/src/nvim/testdir/test17.in b/src/nvim/testdir/test17.in
deleted file mode 100644
index 1a4ac6b6d1..0000000000
--- a/src/nvim/testdir/test17.in
+++ /dev/null
@@ -1,126 +0,0 @@
-Tests for:
-- "gf" on ${VAR},
-- ":checkpath!" with various 'include' settings.
-
-STARTTEST
-:set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
-:"
-:if has("unix")
-:let $CDIR = "."
-/CDIR
-:else
-:let $TDIR = "."
-/TDIR
-:endif
-:" Dummy writing for making that sure gf doesn't fail even if the current
-:" file is modified. It can be occurred when executing the following command
-:" directly on Windows without fixing the 'fileformat':
-:" > nmake -f Make_dos.mak test17.out
-:w! test.out
-gf
-:set ff=unix
-:w! test.out
-:brewind
-ENDTEST
-
- ${CDIR}/test17a.in
- $TDIR/test17a.in
-
-STARTTEST
-:" check for 'include' without \zs or \ze
-:lang C
-:call delete("./Xbase.a")
-:call delete("Xdir1", "rf")
-:!mkdir Xdir1
-:!mkdir "Xdir1/dir2"
-:e! Xdir1/dir2/foo.a
-i#include "bar.a":
-:w
-:e Xdir1/dir2/bar.a
-i#include "baz.a":
-:w
-:e Xdir1/dir2/baz.a
-i#include "foo.a":
-:w
-:e Xbase.a
-:set path=Xdir1/dir2
-i#include <foo.a>:
-:w
-:redir! >>test.out
-:checkpath!
-:redir END
-:brewind
-ENDTEST
-
-STARTTEST
-:" check for 'include' with \zs and \ze
-:call delete("./Xbase.b")
-:call delete("Xdir1", "rf")
-:!mkdir Xdir1
-:!mkdir "Xdir1/dir2"
-:let &include='^\s*%inc\s*/\zs[^/]\+\ze'
-:function! DotsToSlashes()
-: return substitute(v:fname, '\.', '/', 'g') . '.b'
-:endfunction
-:let &includeexpr='DotsToSlashes()'
-:e! Xdir1/dir2/foo.b
-i%inc /bar/:
-:w
-:e Xdir1/dir2/bar.b
-i%inc /baz/:
-:w
-:e Xdir1/dir2/baz.b
-i%inc /foo/:
-:w
-:e Xbase.b
-:set path=Xdir1/dir2
-i%inc /foo/:
-:w
-:redir! >>test.out
-:checkpath!
-:redir END
-:brewind
-ENDTEST
-
-STARTTEST
-:" check for 'include' with \zs and no \ze
-:call delete("./Xbase.c")
-:call delete("Xdir1", "rf")
-:!mkdir Xdir1
-:!mkdir "Xdir1/dir2"
-:let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
-:function! StripNewlineChar()
-: if v:fname =~ '\n$'
-: return v:fname[:-2]
-: endif
-: return v:fname
-:endfunction
-:let &includeexpr='StripNewlineChar()'
-:e! Xdir1/dir2/foo.c
-i%inc bar.c:
-:w
-:e Xdir1/dir2/bar.c
-i%inc baz.c:
-:w
-:e Xdir1/dir2/baz.c
-i%inc foo.c:
-:w
-:e Xdir1/dir2/FALSE.c
-i%inc foo.c:
-:w
-:e Xbase.c
-:set path=Xdir1/dir2
-i%inc FALSE.c foo.c:
-:w
-:redir! >>test.out
-:checkpath!
-:redir END
-:brewind
-:" change "\" to "/" for Windows and fix 'fileformat'
-:e test.out
-:%s#\\#/#g
-:set ff&
-:w
-:q
-ENDTEST
-
diff --git a/src/nvim/testdir/test17.ok b/src/nvim/testdir/test17.ok
deleted file mode 100644
index b2a66d5f85..0000000000
--- a/src/nvim/testdir/test17.ok
+++ /dev/null
@@ -1,33 +0,0 @@
-This file is just to test "gf" in test 17.
-The contents is not important.
-Just testing!
-
-
---- Included files in path ---
-Xdir1/dir2/foo.a
-Xdir1/dir2/foo.a -->
- Xdir1/dir2/bar.a
- Xdir1/dir2/bar.a -->
- Xdir1/dir2/baz.a
- Xdir1/dir2/baz.a -->
- "foo.a" (Already listed)
-
-
---- Included files in path ---
-Xdir1/dir2/foo.b
-Xdir1/dir2/foo.b -->
- Xdir1/dir2/bar.b
- Xdir1/dir2/bar.b -->
- Xdir1/dir2/baz.b
- Xdir1/dir2/baz.b -->
- foo (Already listed)
-
-
---- Included files in path ---
-Xdir1/dir2/foo.c
-Xdir1/dir2/foo.c -->
- Xdir1/dir2/bar.c
- Xdir1/dir2/bar.c -->
- Xdir1/dir2/baz.c
- Xdir1/dir2/baz.c -->
- foo.c (Already listed)
diff --git a/src/nvim/testdir/test17a.in b/src/nvim/testdir/test17a.in
deleted file mode 100644
index 7e89364797..0000000000
--- a/src/nvim/testdir/test17a.in
+++ /dev/null
@@ -1,3 +0,0 @@
-This file is just to test "gf" in test 17.
-The contents is not important.
-Just testing!
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 6d4c54bbf9..1c1da0572b 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -34,6 +34,28 @@ if has('timers')
call timer_start(LoadAdjust(100), 'ExitInsertMode')
call feedkeys('a', 'x!')
call assert_equal(1, g:triggered)
+ unlet g:triggered
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
+ func Test_cursorhold_insert_with_timer_interrupt()
+ if !has('job')
+ return
+ endif
+ " Need to move the cursor.
+ call feedkeys("ggG", "xt")
+
+ " Confirm the timer invoked in exit_cb of the job doesn't disturb
+ " CursorHoldI event.
+ let g:triggered = 0
+ au CursorHoldI * let g:triggered += 1
+ set updatetime=500
+ call job_start(has('win32') ? 'cmd /c echo:' : 'echo',
+ \ {'exit_cb': {j, s -> timer_start(1000, 'ExitInsertMode')}})
+ call feedkeys('a', 'x!')
+ call assert_equal(1, g:triggered)
+ unlet g:triggered
au! CursorHoldI
set updatetime&
endfunc
@@ -46,6 +68,7 @@ if has('timers')
" CursorHoldI does not trigger after CTRL-X
call feedkeys("a\<C-X>", 'x!')
call assert_equal(0, g:triggered)
+ unlet g:triggered
au! CursorHoldI
set updatetime&
endfunc
@@ -457,7 +480,7 @@ endfunc
func Test_OptionSet()
throw 'skipped: Nvim does not support test_override()'
- if !has("eval") || !has("autocmd") || !exists("+autochdir")
+ if !has("eval") || !exists("+autochdir")
return
endif
@@ -598,7 +621,7 @@ endfunc
func Test_OptionSet_diffmode()
throw 'skipped: Nvim does not support test_override()'
call test_override('starting', 1)
- " 18: Changing an option when enetering diff mode
+ " 18: Changing an option when entering diff mode
new
au OptionSet diff :let &l:cul=v:option_new
@@ -654,6 +677,29 @@ func Test_OptionSet_diffmode_close()
"delfunc! AutoCommandOptionSet
endfunc
+func Test_OptionSet_modeline()
+ throw 'skipped: Nvim does not support test_override()'
+ call test_override('starting', 1)
+ au! OptionSet
+ augroup set_tabstop
+ au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")})
+ augroup END
+ call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline')
+ set modeline
+ let v:errmsg = ''
+ call assert_fails('split XoptionsetModeline', 'E12:')
+ call assert_equal(7, &ts)
+ call assert_equal('', v:errmsg)
+
+ augroup set_tabstop
+ au!
+ augroup END
+ bwipe!
+ set ts&
+ call delete('XoptionsetModeline')
+ call test_override('starting', 0)
+endfunc
+
" Test for Bufleave autocommand that deletes the buffer we are about to edit.
func Test_BufleaveWithDelete()
new | edit Xfile1
@@ -1378,3 +1424,249 @@ func Test_autocmd_once()
call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
endfunc
+
+func Test_autocmd_bufreadpre()
+ new
+ let b:bufreadpre = 1
+ call append(0, range(100))
+ w! XAutocmdBufReadPre.txt
+ autocmd BufReadPre <buffer> :let b:bufreadpre += 1
+ norm! 50gg
+ sp
+ norm! 100gg
+ wincmd p
+ let g:wsv1 = winsaveview()
+ wincmd p
+ let g:wsv2 = winsaveview()
+ " triggers BufReadPre, should not move the cursor in either window
+ " The topline may change one line in a large window.
+ edit
+ call assert_inrange(g:wsv2.topline - 1, g:wsv2.topline + 1, winsaveview().topline)
+ call assert_equal(g:wsv2.lnum, winsaveview().lnum)
+ call assert_equal(2, b:bufreadpre)
+ wincmd p
+ call assert_equal(g:wsv1.topline, winsaveview().topline)
+ call assert_equal(g:wsv1.lnum, winsaveview().lnum)
+ call assert_equal(2, b:bufreadpre)
+ " Now set the cursor position in an BufReadPre autocommand
+ " (even though the position will be invalid, this should make Vim reset the
+ " cursor position in the other window.
+ wincmd p
+ 1
+ " won't do anything, but try to set the cursor on an invalid lnum
+ autocmd BufReadPre <buffer> :norm! 70gg
+ " triggers BufReadPre, should not move the cursor in either window
+ e
+ call assert_equal(1, winsaveview().topline)
+ call assert_equal(1, winsaveview().lnum)
+ call assert_equal(3, b:bufreadpre)
+ wincmd p
+ call assert_equal(g:wsv1.topline, winsaveview().topline)
+ call assert_equal(g:wsv1.lnum, winsaveview().lnum)
+ call assert_equal(3, b:bufreadpre)
+ close
+ close
+ call delete('XAutocmdBufReadPre.txt')
+endfunc
+
+" Tests for the following autocommands:
+" - FileWritePre writing a compressed file
+" - FileReadPost reading a compressed file
+" - BufNewFile reading a file template
+" - BufReadPre decompressing the file to be read
+" - FilterReadPre substituting characters in the temp file
+" - FilterReadPost substituting characters after filtering
+" - FileReadPre set options for decompression
+" - FileReadPost decompress the file
+func Test_ReadWrite_Autocmds()
+ " Run this test only on Unix-like systems and if gzip is available
+ if !has('unix') || !executable("gzip")
+ return
+ endif
+
+ " Make $GZIP empty, "-v" would cause trouble.
+ let $GZIP = ""
+
+ " Use a FileChangedShell autocommand to avoid a prompt for 'Xtestfile.gz'
+ " being modified outside of Vim (noticed on Solaris).
+ au FileChangedShell * echo 'caught FileChangedShell'
+
+ " Test for the FileReadPost, FileWritePre and FileWritePost autocmds
+ augroup Test1
+ au!
+ au FileWritePre *.gz '[,']!gzip
+ au FileWritePost *.gz undo
+ au FileReadPost *.gz '[,']!gzip -d
+ augroup END
+
+ new
+ set bin
+ call append(0, [
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ])
+ 1,9write! Xtestfile.gz
+ enew! | close
+
+ new
+ " Read and decompress the testfile
+ 0read Xtestfile.gz
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+ enew! | close
+
+ augroup Test1
+ au!
+ augroup END
+
+ " Test for the FileAppendPre and FileAppendPost autocmds
+ augroup Test2
+ au!
+ au BufNewFile *.c read Xtest.c
+ au FileAppendPre *.out '[,']s/new/NEW/
+ au FileAppendPost *.out !cat Xtest.c >> test.out
+ augroup END
+
+ call writefile(['/*', ' * Here is a new .c file', ' */'], 'Xtest.c')
+ new foo.c " should load Xtest.c
+ call assert_equal(['/*', ' * Here is a new .c file', ' */'], getline(2, 4))
+ w! >> test.out " append it to the output file
+
+ let contents = readfile('test.out')
+ call assert_equal(' * Here is a NEW .c file', contents[2])
+ call assert_equal(' * Here is a new .c file', contents[5])
+
+ call delete('test.out')
+ enew! | close
+ augroup Test2
+ au!
+ augroup END
+
+ " Test for the BufReadPre and BufReadPost autocmds
+ augroup Test3
+ au!
+ " setup autocommands to decompress before reading and re-compress
+ " afterwards
+ au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand("<afile>"))
+ au BufReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
+ au BufReadPost *.gz call rename(expand("<afile>"), expand("<afile>:r"))
+ au BufReadPost *.gz exe '!gzip ' . shellescape(expand("<afile>:r"))
+ augroup END
+
+ e! Xtestfile.gz " Edit compressed file
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+
+ w! >> test.out " Append it to the output file
+
+ augroup Test3
+ au!
+ augroup END
+
+ " Test for the FilterReadPre and FilterReadPost autocmds.
+ set shelltemp " need temp files here
+ augroup Test4
+ au!
+ au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")
+ au FilterReadPre *.out exe 'silent !sed s/e/E/ ' . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))
+ au FilterReadPre *.out exe 'silent !rm ' . shellescape(expand("<afile>")) . '.t'
+ au FilterReadPost *.out '[,']s/x/X/g
+ augroup END
+
+ e! test.out " Edit the output file
+ 1,$!cat
+ call assert_equal([
+ \ 'linE 2 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 4 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 6 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 8 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 10 AbcdefghijklmnopqrstuvwXyz'
+ \ ], getline(1, 9))
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], readfile('test.out'))
+
+ augroup Test4
+ au!
+ augroup END
+ set shelltemp&vim
+
+ " Test for the FileReadPre and FileReadPost autocmds.
+ augroup Test5
+ au!
+ au FileReadPre *.gz exe 'silent !gzip -d ' . shellescape(expand("<afile>"))
+ au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
+ au FileReadPost *.gz '[,']s/l/L/
+ augroup END
+
+ new
+ 0r Xtestfile.gz " Read compressed file
+ call assert_equal([
+ \ 'Line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], readfile('Xtestfile.gz'))
+
+ augroup Test5
+ au!
+ augroup END
+
+ au! FileChangedShell
+ call delete('Xtestfile.gz')
+ call delete('Xtest.c')
+ call delete('test.out')
+endfunc
diff --git a/src/nvim/testdir/test_checkpath.vim b/src/nvim/testdir/test_checkpath.vim
new file mode 100644
index 0000000000..a7557a107d
--- /dev/null
+++ b/src/nvim/testdir/test_checkpath.vim
@@ -0,0 +1,113 @@
+" Tests for the :checkpath command
+
+" Test for 'include' without \zs or \ze
+func Test_checkpath1()
+ let save_shellslash = &shellslash
+ set shellslash
+ call mkdir("Xdir1/dir2", "p")
+ call writefile(['#include "bar.a"'], 'Xdir1/dir2/foo.a')
+ call writefile(['#include "baz.a"'], 'Xdir1/dir2/bar.a')
+ call writefile(['#include "foo.a"'], 'Xdir1/dir2/baz.a')
+ call writefile(['#include <foo.a>'], 'Xbase.a')
+
+ edit Xbase.a
+ set path=Xdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xdir1/dir2/foo.a',
+ \ 'Xdir1/dir2/foo.a -->',
+ \ ' Xdir1/dir2/bar.a',
+ \ ' Xdir1/dir2/bar.a -->',
+ \ ' Xdir1/dir2/baz.a',
+ \ ' Xdir1/dir2/baz.a -->',
+ \ ' "foo.a" (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.a")
+ call delete("Xdir1", "rf")
+ set path&
+ let &shellslash = save_shellslash
+endfunc
+
+func DotsToSlashes()
+ return substitute(v:fname, '\.', '/', 'g') . '.b'
+endfunc
+
+" Test for 'include' with \zs and \ze
+func Test_checkpath2()
+ let save_shellslash = &shellslash
+ set shellslash
+ call mkdir("Xdir1/dir2", "p")
+ call writefile(['%inc /bar/'], 'Xdir1/dir2/foo.b')
+ call writefile(['%inc /baz/'], 'Xdir1/dir2/bar.b')
+ call writefile(['%inc /foo/'], 'Xdir1/dir2/baz.b')
+ call writefile(['%inc /foo/'], 'Xbase.b')
+
+ let &include='^\s*%inc\s*/\zs[^/]\+\ze'
+ let &includeexpr='DotsToSlashes()'
+
+ edit Xbase.b
+ set path=Xdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xdir1/dir2/foo.b',
+ \ 'Xdir1/dir2/foo.b -->',
+ \ ' Xdir1/dir2/bar.b',
+ \ ' Xdir1/dir2/bar.b -->',
+ \ ' Xdir1/dir2/baz.b',
+ \ ' Xdir1/dir2/baz.b -->',
+ \ ' foo (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.b")
+ call delete("Xdir1", "rf")
+ set path&
+ set include&
+ set includeexpr&
+ let &shellslash = save_shellslash
+endfunc
+
+func StripNewlineChar()
+ if v:fname =~ '\n$'
+ return v:fname[:-2]
+ endif
+ return v:fname
+endfunc
+
+" Test for 'include' with \zs and no \ze
+func Test_checkpath3()
+ let save_shellslash = &shellslash
+ set shellslash
+ call mkdir("Xdir1/dir2", "p")
+ call writefile(['%inc bar.c'], 'Xdir1/dir2/foo.c')
+ call writefile(['%inc baz.c'], 'Xdir1/dir2/bar.c')
+ call writefile(['%inc foo.c'], 'Xdir1/dir2/baz.c')
+ call writefile(['%inc foo.c'], 'Xdir1/dir2/FALSE.c')
+ call writefile(['%inc FALSE.c foo.c'], 'Xbase.c')
+
+ let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
+ let &includeexpr='StripNewlineChar()'
+
+ edit Xbase.c
+ set path=Xdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xdir1/dir2/foo.c',
+ \ 'Xdir1/dir2/foo.c -->',
+ \ ' Xdir1/dir2/bar.c',
+ \ ' Xdir1/dir2/bar.c -->',
+ \ ' Xdir1/dir2/baz.c',
+ \ ' Xdir1/dir2/baz.c -->',
+ \ ' foo.c (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.c")
+ call delete("Xdir1", "rf")
+ set path&
+ set include&
+ set includeexpr&
+ let &shellslash = save_shellslash
+endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
new file mode 100644
index 0000000000..db18f7695d
--- /dev/null
+++ b/src/nvim/testdir/test_debugger.vim
@@ -0,0 +1,232 @@
+" Tests for the Vim script debug commands
+
+source shared.vim
+" source screendump.vim
+
+" Run a Vim debugger command
+" If the expected output argument is supplied, then check for it.
+func RunDbgCmd(buf, cmd, ...)
+ call term_sendkeys(a:buf, a:cmd . "\r")
+ call term_wait(a:buf)
+
+ if a:0 != 0
+ " Verify the expected output
+ let lnum = 20 - len(a:1)
+ for l in a:1
+ call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))})
+ let lnum += 1
+ endfor
+ endif
+endfunc
+
+" Debugger tests
+func Test_Debugger()
+ if !CanRunVimInTerminal()
+ return
+ endif
+
+ " Create a Vim script with some functions
+ call writefile([
+ \ 'func Foo()',
+ \ ' let var1 = 1',
+ \ ' let var2 = Bar(var1) + 9',
+ \ ' return var2',
+ \ 'endfunc',
+ \ 'func Bar(var)',
+ \ ' let var1 = 2 + a:var',
+ \ ' let var2 = Bazz(var1) + 4',
+ \ ' return var2',
+ \ 'endfunc',
+ \ 'func Bazz(var)',
+ \ ' let var1 = 3 + a:var',
+ \ ' let var3 = "another var"',
+ \ ' let var3 = "value2"',
+ \ ' let var3 = "value3"',
+ \ ' return var1',
+ \ 'endfunc'], 'Xtest.vim')
+
+ " Start Vim in a terminal
+ let buf = RunVimInTerminal('-S Xtest.vim', {})
+
+ " Start the Vim debugger
+ call RunDbgCmd(buf, ':debug echo Foo()')
+
+ " Create a few stack frames by stepping through functions
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+
+ " check backtrace
+ call RunDbgCmd(buf, 'backtrace', [
+ \ ' 2 function Foo[2]',
+ \ ' 1 Bar[2]',
+ \ '->0 Bazz',
+ \ 'line 2: let var3 = "another var"'])
+
+ " Check variables in different stack frames
+ call RunDbgCmd(buf, 'echo var1', ['6'])
+
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'back', [
+ \ ' 2 function Foo[2]',
+ \ '->1 Bar[2]',
+ \ ' 0 Bazz',
+ \ 'line 2: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'echo var1', ['3'])
+
+ call RunDbgCmd(buf, 'u')
+ call RunDbgCmd(buf, 'bt', [
+ \ '->2 function Foo[2]',
+ \ ' 1 Bar[2]',
+ \ ' 0 Bazz',
+ \ 'line 2: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'echo var1', ['1'])
+
+ " Undefined variables
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'frame 2')
+ call RunDbgCmd(buf, 'echo var3', [
+ \ 'Error detected while processing function Foo[2]..Bar[2]..Bazz:',
+ \ 'line 3:',
+ \ 'E121: Undefined variable: var3'])
+
+ " var3 is defined in this level with some other value
+ call RunDbgCmd(buf, 'fr 0')
+ call RunDbgCmd(buf, 'echo var3', ['another var'])
+
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step', [
+ \ 'function Foo[2]..Bar',
+ \ 'line 3: End of function'])
+ call RunDbgCmd(buf, 'up')
+
+ " Undefined var2
+ call RunDbgCmd(buf, 'echo var2', [
+ \ 'Error detected while processing function Foo[2]..Bar:',
+ \ 'line 3:',
+ \ 'E121: Undefined variable: var2'])
+
+ " Var2 is defined with 10
+ call RunDbgCmd(buf, 'down')
+ call RunDbgCmd(buf, 'echo var2', ['10'])
+
+ " Backtrace movements
+ call RunDbgCmd(buf, 'b', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ " next command cannot go down, we are on bottom
+ call RunDbgCmd(buf, 'down', ['frame is zero'])
+ call RunDbgCmd(buf, 'up')
+
+ " next command cannot go up, we are on top
+ call RunDbgCmd(buf, 'up', ['frame at highest level: 1'])
+ call RunDbgCmd(buf, 'where', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ " fil is not frame or finish, it is file
+ call RunDbgCmd(buf, 'fil', ['"[No Name]" --No lines in buffer--'])
+
+ " relative backtrace movement
+ call RunDbgCmd(buf, 'fr -1')
+ call RunDbgCmd(buf, 'frame', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ call RunDbgCmd(buf, 'fr +1')
+ call RunDbgCmd(buf, 'fram', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ " go beyond limits does not crash
+ call RunDbgCmd(buf, 'fr 100', ['frame at highest level: 1'])
+ call RunDbgCmd(buf, 'fra', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ call RunDbgCmd(buf, 'frame -40', ['frame is zero'])
+ call RunDbgCmd(buf, 'fram', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ " final result 19
+ call RunDbgCmd(buf, 'cont', ['19'])
+
+ " breakpoints tests
+
+ " Start a debug session, so that reading the last line from the terminal
+ " works properly.
+ call RunDbgCmd(buf, ':debug echo Foo()')
+
+ " No breakpoints
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ " Place some breakpoints
+ call RunDbgCmd(buf, 'breaka func Bar')
+ call RunDbgCmd(buf, 'breaklis', [' 1 func Bar line 1'])
+ call RunDbgCmd(buf, 'breakadd func 3 Bazz')
+ call RunDbgCmd(buf, 'breaklist', [' 1 func Bar line 1',
+ \ ' 2 func Bazz line 3'])
+
+ " Check whether the breakpoints are hit
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bar" line 1',
+ \ 'function Foo[2]..Bar',
+ \ 'line 1: let var1 = 2 + a:var'])
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bazz" line 3',
+ \ 'function Foo[2]..Bar[2]..Bazz',
+ \ 'line 3: let var3 = "value2"'])
+
+ " Delete the breakpoints
+ call RunDbgCmd(buf, 'breakd 1')
+ call RunDbgCmd(buf, 'breakli', [' 2 func Bazz line 3'])
+ call RunDbgCmd(buf, 'breakdel func 3 Bazz')
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ call RunDbgCmd(buf, 'cont')
+
+ " Make sure the breakpoints are removed
+ call RunDbgCmd(buf, ':echo Foo()', ['19'])
+
+ " Delete a non-existing breakpoint
+ call RunDbgCmd(buf, ':breakdel 2', ['E161: Breakpoint not found: 2'])
+
+ " Expression breakpoint
+ call RunDbgCmd(buf, ':breakadd func 2 Bazz')
+ call RunDbgCmd(buf, ':echo Bazz(1)')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'breaka expr var3')
+ call RunDbgCmd(buf, 'breakl', [' 4 expr var3'])
+ call RunDbgCmd(buf, 'cont', ['Breakpoint in "Bazz" line 4',
+ \ 'Oldval = "''another var''"',
+ \ 'Newval = "''value2''"',
+ \ 'function Bazz',
+ \ 'line 4: let var3 = "value3"'])
+
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ " finish the current function
+ call RunDbgCmd(buf, 'finish', [
+ \ 'function Bazz',
+ \ 'line 5: End of function'])
+ call RunDbgCmd(buf, 'cont')
+
+ call StopVimInTerminal(buf)
+
+ call delete('Xtest.vim')
+endfunc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index fa7cfaa40c..de0f3ddb06 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1454,3 +1454,19 @@ func Test_leave_insert_autocmd()
au! InsertLeave
iunmap x
endfunc
+
+" Test for inserting characters using CTRL-V followed by a number.
+func Test_edit_special_chars()
+ new
+
+ if has("ebcdic")
+ let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>"
+ else
+ let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
+ endif
+
+ exe "normal " . t
+ call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 111c85bb95..1850fb0cf1 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -21,3 +21,31 @@ func Test_E963()
call assert_fails("let v:oldfiles=''", 'E963:')
call assert_equal(v_o, v:oldfiles)
endfunc
+
+func Test_mkdir_p()
+ call mkdir('Xmkdir/nested', 'p')
+ call assert_true(isdirectory('Xmkdir/nested'))
+ try
+ " Trying to make existing directories doesn't error
+ call mkdir('Xmkdir', 'p')
+ call mkdir('Xmkdir/nested', 'p')
+ catch /E739:/
+ call assert_report('mkdir(..., "p") failed for an existing directory')
+ endtry
+ " 'p' doesn't suppress real errors
+ call writefile([], 'Xfile')
+ call assert_fails('call mkdir("Xfile", "p")', 'E739')
+ call delete('Xfile')
+ call delete('Xmkdir', 'rf')
+endfunc
+
+func Test_line_continuation()
+ let array = [5,
+ "\ ignore this
+ \ 6,
+ "\ more to ignore
+ "\ more moreto ignore
+ \ ]
+ "\ and some more
+ call assert_equal([5, 6], array)
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 5d4a0ff3cb..7a99a37be4 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -200,7 +200,7 @@ let s:filename_checks = {
\ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
\ 'hostconf': ['/etc/host.conf'],
\ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'],
- \ 'htmlcheetah': ['file.tmpl'],
+ \ 'template': ['file.tmpl'],
\ 'htmlm4': ['file.html.m4'],
\ 'httest': ['file.htt', 'file.htb'],
\ 'ibasic': ['file.iba', 'file.ibi'],
@@ -450,6 +450,7 @@ let s:filename_checks = {
\ 'tssgm': ['file.tssgm'],
\ 'tssop': ['file.tssop'],
\ 'twig': ['file.twig'],
+ \ 'typescript': ['file.ts'],
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf'],
\ 'udevperm': ['/etc/udev/permissions.d/file.permissions'],
@@ -484,7 +485,7 @@ let s:filename_checks = {
\ 'xhtml': ['file.xhtml', 'file.xht'],
\ 'xinetd': ['/etc/xinetd.conf'],
\ 'xmath': ['file.msc', 'file.msf'],
- \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ts', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'],
+ \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'],
\ 'xmodmap': ['anyXmodmap'],
\ 'xf86conf': ['xorg.conf', 'xorg.conf-4'],
\ 'xpm2': ['file.xpm2'],
diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim
index 0bae161a8b..f5488a6a27 100644
--- a/src/nvim/testdir/test_findfile.vim
+++ b/src/nvim/testdir/test_findfile.vim
@@ -133,6 +133,7 @@ func Test_finddir()
let save_shellslash = &shellslash
let save_dir = getcwd()
set path=,,
+ set shellslash
call CreateFiles()
cd Xdir1
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index d233046d2b..accd21e9a3 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -64,3 +64,38 @@ func Test_gF()
bwipe Xfile
bwipe Xfile2
endfunc
+
+" Test for invoking 'gf' on a ${VAR} variable
+func Test_gf()
+ if has("ebcdic")
+ set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,}
+ else
+ set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
+ endif
+
+ call writefile(["Test for gf command"], "Xtest1")
+ if has("unix")
+ call writefile([" ${CDIR}/Xtest1"], "Xtestgf")
+ else
+ call writefile([" $TDIR/Xtest1"], "Xtestgf")
+ endif
+ new Xtestgf
+ if has("unix")
+ let $CDIR = "."
+ /CDIR
+ else
+ if has("amiga")
+ let $TDIR = "/testdir"
+ else
+ let $TDIR = "."
+ endif
+ /TDIR
+ endif
+
+ normal gf
+ call assert_equal('Xtest1', fnamemodify(bufname(''), ":t"))
+ close!
+
+ call delete('Xtest1')
+ call delete('Xtestgf')
+endfunc
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index 4899f59910..57cfaa298e 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -90,6 +90,45 @@ func Test_listchars()
\ '.....h>-$',
\ 'iii<<<<><<$', '$'], l)
+
+ " test nbsp
+ normal ggdG
+ set listchars=nbsp:X,trail:Y
+ set list
+ " Non-breaking space
+ let nbsp = nr2char(0xa0)
+ call append(0, [ ">".nbsp."<" ])
+
+ let expected = '>X< '
+
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, virtcol('$')))
+
+ set listchars=nbsp:X
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, virtcol('$')))
+
+ " test extends
+ normal ggdG
+ set listchars=extends:Z
+ set nowrap
+ set nolist
+ call append(0, [ repeat('A', &columns + 1) ])
+
+ let expected = repeat('A', &columns)
+
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, &columns))
+
+ set list
+ let expected = expected[:-2] . 'Z'
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, &columns))
+
enew!
set listchars& ff&
endfunc
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 32593a423a..67f07ff6de 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -4,6 +4,8 @@ if !has('multi_byte')
finish
endif
+source shared.vim
+
func Test_abbreviation()
" abbreviation with 0x80 should work
inoreab чкпр vim
@@ -173,6 +175,9 @@ func Test_abbr_after_line_join()
endfunc
func Test_map_timeout()
+ if !has('timers')
+ return
+ endif
nnoremap aaaa :let got_aaaa = 1<CR>
nnoremap bb :let got_bb = 1<CR>
nmap b aaa
@@ -182,7 +187,7 @@ func Test_map_timeout()
call feedkeys("\<Esc>", "t")
endfunc
set timeout timeoutlen=200
- call timer_start(300, 'ExitInsert')
+ let timer = timer_start(300, 'ExitInsert')
" After the 'b' Vim waits for another character to see if it matches 'bb'.
" When it times out it is expanded to "aaa", but there is no wait for
" "aaaa". Can't check that reliably though.
@@ -197,6 +202,39 @@ func Test_map_timeout()
nunmap b
set timeoutlen&
delfunc ExitInsert
+ call timer_stop(timer)
+endfunc
+
+func Test_map_timeout_with_timer_interrupt()
+ if !has('job') || !has('timers')
+ return
+ endif
+
+ " Confirm the timer invoked in exit_cb of the job doesn't disturb mapped key
+ " sequence.
+ new
+ let g:val = 0
+ nnoremap \12 :let g:val = 1<CR>
+ nnoremap \123 :let g:val = 2<CR>
+ set timeout timeoutlen=1000
+
+ func ExitCb(job, status)
+ let g:timer = timer_start(1, {_ -> feedkeys("3\<Esc>", 't')})
+ endfunc
+
+ call job_start([&shell, &shellcmdflag, 'echo'], {'exit_cb': 'ExitCb'})
+ call feedkeys('\12', 'xt!')
+ call assert_equal(2, g:val)
+
+ bwipe!
+ nunmap \12
+ nunmap \123
+ set timeoutlen&
+ call WaitFor({-> exists('g:timer')})
+ call timer_stop(g:timer)
+ unlet g:timer
+ unlet g:val
+ delfunc ExitCb
endfunc
func Test_cabbr_visual_mode()
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index d07b3fdbce..ef17209f74 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -82,7 +82,6 @@ fun! Test_normal00_optrans()
endfunc
func! Test_normal01_keymodel()
- throw "skipped: Nvim regression: 'keymodel'"
call Setup_NewWindow()
" Test 1: depending on 'keymodel' <s-down> does something different
50
@@ -1353,11 +1352,21 @@ func! Test_normal23_K()
bw!
return
endif
- set keywordprg=man\ --pager=cat
+
+ if has('mac')
+ " In MacOS, the option for specifying a pager is different
+ set keywordprg=man\ -P\ cat
+ else
+ set keywordprg=man\ --pager=cat
+ endif
" Test for using man
2
let a = execute('unsilent norm! K')
- call assert_match("man --pager=cat 'man'", a)
+ if has('mac')
+ call assert_match("man -P cat 'man'", a)
+ else
+ call assert_match("man --pager=cat 'man'", a)
+ endif
" clean up
let &keywordprg = k
@@ -2272,6 +2281,8 @@ endfunc
func! Test_normal45_drop()
if !has('dnd')
+ " The ~ register does not exist
+ call assert_beeps('norm! "~')
return
endif
@@ -2540,3 +2551,81 @@ func Test_delete_until_paragraph()
call assert_equal('', getline(1))
bwipe!
endfunc
+
+" Test for '[m', ']m', '[M' and ']M'
+" Jumping to beginning and end of methods in Java-like languages
+func Test_java_motion()
+ new
+ a
+Piece of Java
+{
+ tt m1 {
+ t1;
+ } e1
+
+ tt m2 {
+ t2;
+ } e2
+
+ tt m3 {
+ if (x)
+ {
+ t3;
+ }
+ } e3
+}
+.
+
+ normal gg
+
+ normal 2]maA
+ call assert_equal("\ttt m1 {A", getline('.'))
+ call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal j]maB
+ call assert_equal("\ttt m2 {B", getline('.'))
+ call assert_equal([7, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal ]maC
+ call assert_equal("\ttt m3 {C", getline('.'))
+ call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal [maD
+ call assert_equal("\ttt m3 {DC", getline('.'))
+ call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal k2[maE
+ call assert_equal("\ttt m1 {EA", getline('.'))
+ call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal 3[maF
+ call assert_equal("{F", getline('.'))
+ call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ normal ]MaG
+ call assert_equal("\t}G e1", getline('.'))
+ call assert_equal([5, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal j2]MaH
+ call assert_equal("\t}H e3", getline('.'))
+ call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal ]M]M
+ normal aI
+ call assert_equal("}I", getline('.'))
+ call assert_equal([17, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ normal 2[MaJ
+ call assert_equal("\t}JH e3", getline('.'))
+ call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal k[MaK
+ call assert_equal("\t}K e2", getline('.'))
+ call assert_equal([9, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal 3[MaL
+ call assert_equal("{LF", getline('.'))
+ call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index f50f33d255..c6d083dfcc 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -136,6 +136,19 @@ func XlistTests(cchar)
\ ' 4:40 col 20 x 44: Other',
\ ' 5:50 col 25 55: one'], l)
+ " Test for module names, one needs to explicitly set `'valid':v:true` so
+ let save_shellslash = &shellslash
+ set shellslash
+ call g:Xsetlist([
+ \ {'lnum':10,'col':5,'type':'W','module':'Data.Text','text':'ModuleWarning','nr':11,'valid':v:true},
+ \ {'lnum':20,'col':10,'type':'W','module':'Data.Text','filename':'Data/Text.hs','text':'ModuleWarning','nr':22,'valid':v:true},
+ \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}])
+ let l = split(execute('Xlist', ""), "\n")
+ call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning',
+ \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
+ \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l)
+ let &shellslash = save_shellslash
+
" Error cases
call assert_fails('Xlist abc', 'E488:')
endfunc
@@ -1093,6 +1106,21 @@ func Test_efm2()
call assert_equal(1, l[4].valid)
call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr))
+ " Test for %o
+ set efm=%f(%o):%l\ %m
+ cgetexpr ['Xtestfile(Language.PureScript.Types):20 Error']
+ call writefile(['Line1'], 'Xtestfile')
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('Language.PureScript.Types', l[0].module)
+ copen
+ call assert_equal('Language.PureScript.Types|20| Error', getline(1))
+ call feedkeys("\<CR>", 'xn')
+ call assert_equal('Xtestfile', expand('%:t'))
+ cclose
+ bd
+ call delete("Xtestfile")
+
" The following sequence of commands used to crash Vim
set efm=%W%m
cgetexpr ['msg1']
@@ -1379,6 +1407,11 @@ func XquickfixSetListWithAct(cchar)
call assert_fails("call g:Xsetlist(list1, 0)", 'E928:')
endfunc
+func Test_setqflist_invalid_nr()
+ " The following command used to crash Vim
+ call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST})
+endfunc
+
func Test_quickfix_set_list_with_act()
call XquickfixSetListWithAct('c')
call XquickfixSetListWithAct('l')
@@ -1779,8 +1812,8 @@ func Xproperty_tests(cchar)
call assert_equal(-1, s)
call assert_equal({}, g:Xgetlist({'abc':1}))
- call assert_equal({}, g:Xgetlist({'nr':99, 'title':1}))
- call assert_equal({}, g:Xgetlist({'nr':[], 'title':1}))
+ call assert_equal('', g:Xgetlist({'nr':99, 'title':1}).title)
+ call assert_equal('', g:Xgetlist({'nr':[], 'title':1}).title)
if a:cchar == 'l'
call assert_equal({}, getloclist(99, {'title': 1}))
@@ -1816,7 +1849,7 @@ func Xproperty_tests(cchar)
call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
only
call setloclist(0, [], 'f')
- call assert_equal({}, getloclist(0, {'context':1}))
+ call assert_equal('', getloclist(0, {'context':1}).context)
endif
" Test for changing the context of previous quickfix lists
@@ -1948,6 +1981,30 @@ func Test_Autocmd()
cexpr "F1:10:Line 10"
caddexpr "F1:20:Line 20"
cgetexpr "F1:30:Line 30"
+ cexpr ""
+ caddexpr ""
+ cgetexpr ""
+ silent! cexpr non_existing_func()
+ silent! caddexpr non_existing_func()
+ silent! cgetexpr non_existing_func()
+ let l = ['precexpr',
+ \ 'postcexpr',
+ \ 'precaddexpr',
+ \ 'postcaddexpr',
+ \ 'precgetexpr',
+ \ 'postcgetexpr',
+ \ 'precexpr',
+ \ 'postcexpr',
+ \ 'precaddexpr',
+ \ 'postcaddexpr',
+ \ 'precgetexpr',
+ \ 'postcgetexpr',
+ \ 'precexpr',
+ \ 'precaddexpr',
+ \ 'precgetexpr']
+ call assert_equal(l, g:acmds)
+
+ let g:acmds = []
enew! | call append(0, "F2:10:Line 10")
cbuffer!
enew! | call append(0, "F2:20:Line 20")
@@ -1955,19 +2012,108 @@ func Test_Autocmd()
enew! | call append(0, "F2:30:Line 30")
caddbuffer
- let l = ['precexpr',
- \ 'postcexpr',
- \ 'precaddexpr',
- \ 'postcaddexpr',
- \ 'precgetexpr',
- \ 'postcgetexpr',
- \ 'precbuffer',
+ new
+ let bnum = bufnr('%')
+ bunload
+ exe 'silent! cbuffer! ' . bnum
+ exe 'silent! cgetbuffer ' . bnum
+ exe 'silent! caddbuffer ' . bnum
+ enew!
+ let l = ['precbuffer',
\ 'postcbuffer',
\ 'precgetbuffer',
\ 'postcgetbuffer',
\ 'precaddbuffer',
- \ 'postcaddbuffer']
+ \ 'postcaddbuffer',
+ \ 'precbuffer',
+ \ 'precgetbuffer',
+ \ 'precaddbuffer']
+ call assert_equal(l, g:acmds)
+
+ call writefile(['Xtest:1:Line1'], 'Xtest')
+ call writefile([], 'Xempty')
+ let g:acmds = []
+ cfile Xtest
+ caddfile Xtest
+ cgetfile Xtest
+ cfile Xempty
+ caddfile Xempty
+ cgetfile Xempty
+ silent! cfile do_not_exist
+ silent! caddfile do_not_exist
+ silent! cgetfile do_not_exist
+ let l = ['precfile',
+ \ 'postcfile',
+ \ 'precaddfile',
+ \ 'postcaddfile',
+ \ 'precgetfile',
+ \ 'postcgetfile',
+ \ 'precfile',
+ \ 'postcfile',
+ \ 'precaddfile',
+ \ 'postcaddfile',
+ \ 'precgetfile',
+ \ 'postcgetfile',
+ \ 'precfile',
+ \ 'postcfile',
+ \ 'precaddfile',
+ \ 'postcaddfile',
+ \ 'precgetfile',
+ \ 'postcgetfile']
call assert_equal(l, g:acmds)
+
+ let g:acmds = []
+ helpgrep quickfix
+ silent! helpgrep non_existing_help_topic
+ vimgrep test Xtest
+ vimgrepadd test Xtest
+ silent! vimgrep non_existing_test Xtest
+ silent! vimgrepadd non_existing_test Xtest
+ set makeprg=
+ silent! make
+ set makeprg&
+ let l = ['prehelpgrep',
+ \ 'posthelpgrep',
+ \ 'prehelpgrep',
+ \ 'posthelpgrep',
+ \ 'previmgrep',
+ \ 'postvimgrep',
+ \ 'previmgrepadd',
+ \ 'postvimgrepadd',
+ \ 'previmgrep',
+ \ 'postvimgrep',
+ \ 'previmgrepadd',
+ \ 'postvimgrepadd',
+ \ 'premake',
+ \ 'postmake']
+ call assert_equal(l, g:acmds)
+
+ if has('unix')
+ " Run this test only on Unix-like systems. The grepprg may not be set on
+ " non-Unix systems.
+ " The following lines are used for the grep test. Don't remove.
+ " Grep_Autocmd_Text: Match 1
+ " GrepAdd_Autocmd_Text: Match 2
+ let g:acmds = []
+ silent grep Grep_Autocmd_Text test_quickfix.vim
+ silent grepadd GrepAdd_Autocmd_Text test_quickfix.vim
+ silent grep abc123def Xtest
+ silent grepadd abc123def Xtest
+ let l = ['pregrep',
+ \ 'postgrep',
+ \ 'pregrepadd',
+ \ 'postgrepadd',
+ \ 'pregrep',
+ \ 'postgrep',
+ \ 'pregrepadd',
+ \ 'postgrepadd']
+ call assert_equal(l, g:acmds)
+ endif
+
+ call delete('Xtest')
+ call delete('Xempty')
+ au! QuickFixCmdPre
+ au! QuickFixCmdPost
endfunc
func Test_Autocmd_Exception()
@@ -2219,8 +2365,8 @@ func XsizeTests(cchar)
call g:Xsetlist([], 'f')
call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
- call assert_equal(1, len(g:Xgetlist({'nr':'$', 'all':1})))
- call assert_equal(0, len(g:Xgetlist({'nr':0})))
+ call assert_equal('', g:Xgetlist({'nr':'$', 'all':1}).title)
+ call assert_equal(0, g:Xgetlist({'nr':0}).nr)
Xexpr "File1:10:Line1"
Xexpr "File2:20:Line2"
@@ -2440,6 +2586,29 @@ func Xmultifilestack_tests(cchar)
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)
+
+ " Test for start of a new error line in the same line where a previous
+ " error line ends with a file stack.
+ let efm_val = 'Error\ l%l\ in\ %f,'
+ let efm_val .= '%-P%>(%f%r,Error\ l%l\ in\ %m,%-Q)%r'
+ let l = g:Xgetlist({'lines' : [
+ \ '(one.txt',
+ \ 'Error l4 in one.txt',
+ \ ') (two.txt',
+ \ 'Error l6 in two.txt',
+ \ ')',
+ \ 'Error l8 in one.txt'
+ \ ], 'efm' : efm_val})
+ call assert_equal(3, len(l.items))
+ call assert_equal('one.txt', bufname(l.items[0].bufnr))
+ call assert_equal(4, l.items[0].lnum)
+ call assert_equal('one.txt', l.items[0].text)
+ call assert_equal('two.txt', bufname(l.items[1].bufnr))
+ call assert_equal(6, l.items[1].lnum)
+ call assert_equal('two.txt', l.items[1].text)
+ call assert_equal('one.txt', bufname(l.items[2].bufnr))
+ call assert_equal(8, l.items[2].lnum)
+ call assert_equal('', l.items[2].text)
endfunc
func Test_multifilestack()
@@ -2619,7 +2788,7 @@ func Xqfid_tests(cchar)
call s:setup_commands(a:cchar)
call g:Xsetlist([], 'f')
- call assert_equal({}, g:Xgetlist({'id':0}))
+ call assert_equal(0, g:Xgetlist({'id':0}).id)
Xexpr ''
let start_id = g:Xgetlist({'id' : 0}).id
Xexpr '' | Xexpr ''
@@ -2627,10 +2796,10 @@ func Xqfid_tests(cchar)
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(0, g:Xgetlist({'id':0, 'nr':99}).id)
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 assert_equal(0, g:Xgetlist({'id':99, 'nr':0}).id)
+ call assert_equal(0, g:Xgetlist({'id':"abc", 'nr':0}).id)
call g:Xsetlist([], 'a', {'id':start_id, 'context':[1,2]})
call assert_equal([1,2], g:Xgetlist({'nr':1, 'context':1}).context)
@@ -2641,7 +2810,7 @@ func Xqfid_tests(cchar)
let qfid = g:Xgetlist({'id':0, 'nr':0})
call g:Xsetlist([], 'f')
- call assert_equal({}, g:Xgetlist({'id':qfid, 'nr':0}))
+ call assert_equal(0, g:Xgetlist({'id':qfid, 'nr':0}).id)
endfunc
func Test_qf_id()
@@ -2649,6 +2818,15 @@ func Test_qf_id()
call Xqfid_tests('l')
endfunc
+func Test_getqflist_invalid_nr()
+ " The following commands used to crash Vim
+ cexpr ""
+ call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
+
+ " Cleanup
+ call setqflist([], 'r')
+endfunc
+
" Test for shortening/simplifying the file name when opening the
" quickfix window or when displaying the quickfix list
func Test_shorten_fname()
@@ -2673,6 +2851,99 @@ func Test_shorten_fname()
call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
endfunc
+" Quickfix title tests
+" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
+" Otherwise due to indentation, the title is set with spaces at the beginning
+" of the command.
+func Test_qftitle()
+ call writefile(["F1:1:Line1"], 'Xerr')
+
+ " :cexpr
+ exe "cexpr readfile('Xerr')"
+ call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
+
+ " :cgetexpr
+ exe "cgetexpr readfile('Xerr')"
+ call assert_equal(":cgetexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :caddexpr
+ call setqflist([], 'f')
+ exe "caddexpr readfile('Xerr')"
+ call assert_equal(":caddexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :cbuffer
+ new Xerr
+ exe "cbuffer"
+ call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cgetbuffer
+ edit Xerr
+ exe "cgetbuffer"
+ call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :caddbuffer
+ call setqflist([], 'f')
+ edit Xerr
+ exe "caddbuffer"
+ call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cfile
+ exe "cfile Xerr"
+ call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
+
+ " :cgetfile
+ exe "cgetfile Xerr"
+ call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
+
+ " :caddfile
+ call setqflist([], 'f')
+ exe "caddfile Xerr"
+ call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
+
+ " :grep
+ set grepprg=internal
+ exe "grep F1 Xerr"
+ call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :grepadd
+ call setqflist([], 'f')
+ exe "grepadd F1 Xerr"
+ call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
+ set grepprg&vim
+
+ " :vimgrep
+ exe "vimgrep F1 Xerr"
+ call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :vimgrepadd
+ call setqflist([], 'f')
+ exe "vimgrepadd F1 Xerr"
+ call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+
+ call setqflist(['F1:10:L10'], ' ')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'a')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'r')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ close
+ call delete('Xerr')
+
+ call setqflist([], ' ', {'title' : 'Errors'})
+ copen
+ call assert_equal('Errors', w:quickfix_title)
+ call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
+ call assert_equal('Errors', w:quickfix_title)
+ cclose
+endfunc
+
" Test for the position of the quickfix and location list window
func Test_qfwin_pos()
" Open two windows
@@ -2700,6 +2971,20 @@ func Test_qfwin_pos()
close
endfunc
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd()
+ lhelpgrep quickfix
+ autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
+ lhelpgrep buffer
+ call assert_equal('help', &filetype)
+ call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
+ lhelpgrep tabpage
+ call assert_equal('help', &filetype)
+ call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
+ au! QuickFixCmdPost
+ new | only
+endfunc
+
" Test to make sure that an empty quickfix buffer is not reused for loading
" a normal buffer.
func Test_empty_qfbuf()
@@ -2713,3 +2998,170 @@ func Test_empty_qfbuf()
enew
call delete('Xfile1')
endfunc
+
+" Tests for the getqflist() and getloclist() functions when the list is not
+" present or is empty
+func Xgetlist_empty_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Empty quickfix stack
+ call g:Xsetlist([], 'f')
+ call assert_equal('', g:Xgetlist({'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick': 0}, g:Xgetlist({'all' : 0}))
+
+ " Quickfix window with empty stack
+ silent! Xopen
+ let qfwinid = (a:cchar == 'c') ? win_getid() : 0
+ call assert_equal(qfwinid, g:Xgetlist({'winid' : 0}).winid)
+ Xclose
+
+ " Empty quickfix list
+ Xexpr ""
+ call assert_equal('', g:Xgetlist({'context' : 0}).context)
+ call assert_notequal(0, g:Xgetlist({'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'items' : 0}).items)
+ call assert_notequal(0, g:Xgetlist({'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ call assert_notequal('', g:Xgetlist({'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ let qfid = g:Xgetlist({'id' : 0}).id
+ call g:Xsetlist([], 'f')
+
+ " Non-existing quickfix identifier
+ call assert_equal('', g:Xgetlist({'id' : qfid, 'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'id' : qfid}).id)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'id' : qfid, 'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'id' : qfid, 'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'changedtick' : 0}).changedtick)
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+
+ " Non-existing quickfix list number
+ call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'nr' : 5}).nr)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'nr' : 5, 'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'nr' : 5, 'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'changedtick' : 0}).changedtick)
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+endfunc
+
+func Test_getqflist()
+ call Xgetlist_empty_tests('c')
+ call Xgetlist_empty_tests('l')
+endfunc
+
+" Tests for the quickfix/location list changedtick
+func Xqftick_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ Xexpr "F1:10:Line10"
+ let qfid = g:Xgetlist({'id' : 0}).id
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ Xaddexpr "F2:20:Line20\nF2:21:Line21"
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'a', {'lines' : ["F3:30:Line30", "F3:31:Line31"]})
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'r', {'lines' : ["F4:40:Line40"]})
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'a', {'title' : 'New Title'})
+ call assert_equal(5, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ enew!
+ call append(0, ["F5:50:L50", "F6:60:L60"])
+ Xaddbuffer
+ call assert_equal(6, g:Xgetlist({'changedtick' : 0}).changedtick)
+ enew!
+
+ call g:Xsetlist([], 'a', {'context' : {'bus' : 'pci'}})
+ call assert_equal(7, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'a')
+ call assert_equal(8, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], ' ')
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ call writefile(["F8:80:L80", "F8:81:L81"], "Xone")
+ Xfile Xone
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ Xaddfile Xone
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test case for updating a non-current quickfix list
+ call g:Xsetlist([], 'f')
+ Xexpr "F1:1:L1"
+ Xexpr "F2:2:L2"
+ call g:Xsetlist([], 'a', {'nr' : 1, "lines" : ["F10:10:L10"]})
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call assert_equal(2, g:Xgetlist({'nr' : 1, 'changedtick' : 0}).changedtick)
+
+ call delete("Xone")
+endfunc
+
+func Test_qf_tick()
+ call Xqftick_tests('c')
+ call Xqftick_tests('l')
+endfunc
+
+" The following test used to crash vim
+func Test_lbuffer_crash()
+ sv Xtest
+ augroup QF_Test
+ au!
+ au * * bw
+ augroup END
+ lbuffer
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" The following test used to crash vim
+func Test_lexpr_crash()
+ augroup QF_Test
+ au!
+ au * * call setloclist(0, [], 'f')
+ augroup END
+ lexpr ""
+ augroup QF_Test
+ au!
+ augroup END
+ enew | only
+endfunc
+
+" The following test used to crash Vim
+func Test_lvimgrep_crash()
+ sv Xtest
+ augroup QF_Test
+ au!
+ au * * call setloclist(0, [], 'f')
+ augroup END
+ lvimgrep quickfix test_quickfix.vim
+ augroup QF_Test
+ au!
+ augroup END
+ enew | only
+endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 08ccc8d4fe..f4fe4051e3 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -585,3 +585,27 @@ func Test_one_error_msg()
" This was also giving an internal error
call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:')
endfunc
+
+" Test for the search() function with match at the cursor position
+func Test_search_match_at_curpos()
+ new
+ call append(0, ['foobar', '', 'one two', ''])
+
+ normal gg
+
+ call search('foobar', 'c')
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ normal j
+ call search('^$', 'c')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ call search('^$', 'bc')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ exe "normal /two\<CR>"
+ call search('.', 'c')
+ call assert_equal([3, 5], [line('.'), col('.')])
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 2738b00f2f..e074a85530 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -641,3 +641,52 @@ func Test_nocatch_sub_failure_handling()
bwipe!
endfunc
+
+" Test ":s/pat/sub/" with different ~s in sub.
+func Test_replace_with_tilde()
+ new
+ " Set the last replace string to empty
+ s/^$//
+ call append(0, ['- Bug in "vPPPP" on this text:'])
+ normal gg
+ s/u/~u~/
+ call assert_equal('- Bug in "vPPPP" on this text:', getline(1))
+ s/i/~u~/
+ call assert_equal('- Bug uuun "vPPPP" on this text:', getline(1))
+ s/o/~~~/
+ call assert_equal('- Bug uuun "vPPPP" uuuuuuuuun this text:', getline(1))
+ close!
+endfunc
+
+func Test_replace_keeppatterns()
+ new
+ a
+foobar
+
+substitute foo asdf
+
+one two
+.
+
+ normal gg
+ /^substitute
+ s/foo/bar/
+ call assert_equal('foo', @/)
+ call assert_equal('substitute bar asdf', getline('.'))
+
+ /^substitute
+ keeppatterns s/asdf/xyz/
+ call assert_equal('^substitute', @/)
+ call assert_equal('substitute bar xyz', getline('.'))
+
+ exe "normal /bar /e\<CR>"
+ call assert_equal(15, col('.'))
+ normal -
+ keeppatterns /xyz
+ call assert_equal('bar ', @/)
+ call assert_equal('substitute bar xyz', getline('.'))
+ exe "normal 0dn"
+ call assert_equal('xyz', getline('.'))
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index bc7b7c00d3..3db438cf4b 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -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&
@@ -47,6 +51,42 @@ func Test_swap_directory()
call delete("Xtest.je", "rf")
endfunc
+func Test_swap_group()
+ if !has("unix")
+ return
+ endif
+ let groups = split(system('groups'))
+ if len(groups) <= 1
+ throw 'Skipped: need at least two groups, got ' . string(groups)
+ endif
+
+ try
+ call delete('Xtest')
+ split Xtest
+ call setline(1, 'just some text')
+ wq
+ if system('ls -l Xtest') !~ ' ' . groups[0] . ' \d'
+ throw 'Skipped: test file does not have the first group'
+ else
+ silent !chmod 640 Xtest
+ call system('chgrp ' . groups[1] . ' Xtest')
+ if system('ls -l Xtest') !~ ' ' . groups[1] . ' \d'
+ throw 'Skipped: cannot set second group on test file'
+ else
+ split Xtest
+ let swapname = substitute(execute('swapname'), '[[:space:]]', '', 'g')
+ call assert_match('Xtest', swapname)
+ " Group of swapfile must now match original file.
+ call assert_match(' ' . groups[1] . ' \d', system('ls -l ' . swapname))
+
+ bwipe!
+ endif
+ endif
+ finally
+ call delete('Xtest')
+ endtry
+endfunc
+
func Test_missing_dir()
call mkdir('Xswapdir')
exe 'set directory=' . getcwd() . '/Xswapdir'
@@ -61,3 +101,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
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 6978faeb7b..850947f89f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -503,3 +503,37 @@ func Test_syn_wrong_z_one()
" call test_override("ALL", 0)
bwipe!
endfunc
+
+func Test_syntax_hangs()
+ if !has('reltime') || !has('float') || !has('syntax')
+ return
+ endif
+
+ " This pattern takes a long time to match, it should timeout.
+ new
+ call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
+ let start = reltime()
+ set nolazyredraw redrawtime=101
+ syn match Error /\%#=1a*.*X\@<=b*/
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ " second time syntax HL is disabled
+ let start = reltime()
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed < 0.1)
+
+ " after CTRL-L the timeout flag is reset
+ let start = reltime()
+ exe "normal \<C-L>"
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ set redrawtime&
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index add9b3d7cf..8043d13433 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -1,5 +1,6 @@
" Tests for tabpage
+" source screendump.vim
function Test_tabpage()
bw!
@@ -105,6 +106,14 @@ function Test_tabpage()
call assert_equal(4, tabpagenr())
7tabmove 5
call assert_equal(5, tabpagenr())
+ -tabmove
+ call assert_equal(4, tabpagenr())
+ +tabmove
+ call assert_equal(5, tabpagenr())
+ -2tabmove
+ call assert_equal(3, tabpagenr())
+ +3tabmove
+ call assert_equal(6, tabpagenr())
" The following are a no-op
norm! 2gt
@@ -547,4 +556,27 @@ func Test_tabs()
bw!
endfunc
+func Test_tabpage_cmdheight()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: only works with terminal'
+ endif
+ call writefile([
+ \ 'set laststatus=2',
+ \ 'set cmdheight=2',
+ \ 'tabnew',
+ \ 'set cmdheight=3',
+ \ 'tabnext',
+ \ 'redraw!',
+ \ 'echo "hello\nthere"',
+ \ 'tabnext',
+ \ 'redraw',
+ \ ], 'XTest_tabpage_cmdheight')
+ " Check that cursor line is concealed
+ let buf = RunVimInTerminal('-S XTest_tabpage_cmdheight', {'statusoff': 3})
+ call VerifyScreenDump(buf, 'Test_tabpage_cmdheight', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XTest_tabpage_cmdheight')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index ad1f5fab73..21ea00cab7 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -258,6 +258,107 @@ func Test_tagjump_etags()
bwipe!
endfunc
+" Test for getting and modifying the tag stack
+func Test_getsettagstack()
+ call writefile(['line1', 'line2', 'line3'], 'Xfile1')
+ call writefile(['line1', 'line2', 'line3'], 'Xfile2')
+ call writefile(['line1', 'line2', 'line3'], 'Xfile3')
+
+ enew | only
+ call settagstack(1, {'items' : []})
+ call assert_equal(0, gettagstack(1).length)
+ call assert_equal([], gettagstack(1).items)
+ " Error cases
+ call assert_equal({}, gettagstack(100))
+ call assert_equal(-1, settagstack(100, {'items' : []}))
+ call assert_fails('call settagstack(1, [1, 10])', 'E715')
+ call assert_fails("call settagstack(1, {'items' : 10})", 'E714')
+ call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928')
+ call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962')
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile1\t1",
+ \ "three\tXfile3\t3",
+ \ "two\tXfile2\t2"],
+ \ 'Xtags')
+
+ let stk = []
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'one',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag one
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'two',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag two
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'three',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag three
+ call assert_equal(3, gettagstack(1).length)
+ call assert_equal(stk, gettagstack(1).items)
+ " Check for default - current window
+ call assert_equal(3, gettagstack().length)
+ call assert_equal(stk, gettagstack().items)
+
+ " Try to set current index to invalid values
+ call settagstack(1, {'curidx' : -1})
+ call assert_equal(1, gettagstack().curidx)
+ call settagstack(1, {'curidx' : 50})
+ call assert_equal(4, gettagstack().curidx)
+
+ " Try pushing invalid items onto the stack
+ call settagstack(1, {'items' : []})
+ call settagstack(1, {'items' : ["plate"]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"tagname" : "abc"}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"from" : 100}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"from" : [2, 1, 0, 0]}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+
+ " Push one item at a time to the stack
+ call settagstack(1, {'items' : []})
+ call settagstack(1, {'items' : [stk[0]]}, 'a')
+ call settagstack(1, {'items' : [stk[1]]}, 'a')
+ call settagstack(1, {'items' : [stk[2]]}, 'a')
+ call settagstack(1, {'curidx' : 4})
+ call assert_equal({'length' : 3, 'curidx' : 4, 'items' : stk},
+ \ gettagstack(1))
+
+ " Try pushing items onto a full stack
+ for i in range(7)
+ call settagstack(1, {'items' : stk}, 'a')
+ endfor
+ call assert_equal(20, gettagstack().length)
+ call settagstack(1,
+ \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a')
+ call assert_equal('abc', gettagstack().items[19].tagname)
+
+ " Tag with multiple matches
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "two\tXfile1\t1",
+ \ "two\tXfile2\t3",
+ \ "two\tXfile3\t2"],
+ \ 'Xtags')
+ call settagstack(1, {'items' : []})
+ tag two
+ tnext
+ tnext
+ call assert_equal(1, gettagstack().length)
+ call assert_equal(3, gettagstack().items[0].matchnr)
+
+ call settagstack(1, {'items' : []})
+ call delete('Xfile1')
+ call delete('Xfile2')
+ call delete('Xfile3')
+ call delete('Xtags')
+ set tags&
+endfunc
+
func Test_tag_with_count()
call writefile([
\ 'test Xtest.h /^void test();$/;" p typeref:typename:void signature:()',
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index f4e939254a..ea0a6b9678 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -1,4 +1,4 @@
-" test 'taglist' function and :tags command
+" test taglist(), tagfiles() functions and :tags command
func Test_taglist()
call writefile([
@@ -74,3 +74,26 @@ func Test_tagsfile_without_trailing_newline()
call delete('Xtags')
endfunc
+
+func Test_tagfiles()
+ call assert_equal([], tagfiles())
+
+ call writefile(["FFoo\tXfoo\t1"], 'Xtags1')
+ call writefile(["FBar\tXbar\t1"], 'Xtags2')
+ set tags=Xtags1,Xtags2
+ call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
+
+ help
+ let tf = tagfiles()
+ call assert_equal(1, len(tf))
+ call assert_equal(fnamemodify(expand('$VIMRUNTIME/doc/tags'), ':p:gs?\\?/?'),
+ \ fnamemodify(tf[0], ':p:gs?\\?/?'))
+ helpclose
+ call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
+ set tags&
+ call assert_equal([], tagfiles())
+
+ call delete('Xtags1')
+ call delete('Xtags2')
+ bd
+endfunc
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 9729ca9f57..beb44f9699 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -242,6 +242,7 @@ func Test_undojoin()
endfunc
func Test_undo_write()
+ call delete('Xtest')
split Xtest
call feedkeys("ione one one\<Esc>", 'xt')
w!
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 74afc72f03..f69273635c 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -411,3 +411,27 @@ func Test_Visual_paragraph_textobject()
bwipe!
endfunc
+
+" Tests for "vaBiB", end could be wrong.
+func Test_Visual_Block()
+ new
+ a
+- Bug in "vPPPP" on this text:
+ {
+ cmd;
+ {
+ cmd;\t/* <-- Start cursor here */
+ {
+ }
+ }
+ }
+.
+ normal gg
+ call search('Start cursor here')
+ normal vaBiBD
+ call assert_equal(['- Bug in "vPPPP" on this text:',
+ \ "\t{",
+ \ "\t}"], getline(1, '$'))
+
+ close!
+endfunc
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 04decb1a8c..6d9023bd79 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -49,15 +49,7 @@ void tinput_init(TermInput *input, Loop *loop)
int curflags = termkey_get_canonflags(input->tk);
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
-#ifdef WIN32
- uv_tty_init(&loop->uv, &input->tty_in, 0, 1);
- uv_tty_set_mode(&input->tty_in, UV_TTY_MODE_RAW);
- rstream_init_stream(&input->read_stream,
- (uv_stream_t *)&input->tty_in,
- 0xfff);
-#else
rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff);
-#endif
// initialize a timer handle for handling ESC with libtermkey
time_watcher_init(loop, &input->timer_handle, input);
}
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index 573cc9d683..7d59cf5c6a 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -17,9 +17,6 @@ typedef struct term_input {
#endif
TimeWatcher timer_handle;
Loop *loop;
-#ifdef WIN32
- uv_tty_t tty_in;
-#endif
Stream read_stream;
RBuffer *key_buffer;
uv_mutex_t key_buffer_mutex;
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 0603e504ec..42e5b9b270 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -704,14 +704,6 @@ static void cursor_goto(UI *ui, int row, int col)
// even less expensive than using BSes or CUB.
unibi_out(ui, unibi_carriage_return);
ugrid_goto(grid, grid->row, 0);
- } else if (col > grid->col) {
- int n = col - grid->col;
- if (n <= (row == grid->row ? 4 : 2)
- && cheap_to_print(ui, grid->row, grid->col, n)) {
- UGRID_FOREACH_CELL(grid, grid->row, grid->col, col, {
- print_cell(ui, cell);
- });
- }
}
if (row == grid->row) {
if (col < grid->col
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 0dddfe5f59..7dbb8ec790 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -141,6 +141,17 @@ bool ui_rgb_attached(void)
return false;
}
+/// Returns true if any UI requested `override=true`.
+bool ui_override(void)
+{
+ for (size_t i = 1; i < ui_count; i++) {
+ if (uis[i]->override) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool ui_active(void)
{
return ui_count > 1;
@@ -173,12 +184,13 @@ void ui_refresh(void)
ext_widgets[i] = true;
}
+ bool inclusive = ui_override();
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
width = MIN(ui->width, width);
height = MIN(ui->height, height);
for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
- ext_widgets[j] &= ui->ui_ext[j];
+ ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
}
}
@@ -431,6 +443,7 @@ Array ui_array(void)
PUT(info, "width", INTEGER_OBJ(ui->width));
PUT(info, "height", INTEGER_OBJ(ui->height));
PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
+ PUT(info, "override", BOOLEAN_OBJ(ui->override));
for (UIExtension j = 0; j < kUIExtCount; j++) {
if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 3f6b3babad..e1dd18a289 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -48,9 +48,11 @@ typedef int LineFlags;
struct ui_t {
bool rgb;
+ bool override; ///< Force highest-requested UI capabilities.
bool composed;
- bool ui_ext[kUIExtCount]; ///< Externalized widgets
- int width, height;
+ bool ui_ext[kUIExtCount]; ///< Externalized UI capabilities.
+ int width;
+ int height;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 8bb1b9e745..10996d99d5 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -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;
}
@@ -1818,7 +1819,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
u_header_T *uhp = NULL;
u_header_T *last;
int mark;
- int nomark;
+ int nomark = 0; // shut up compiler
int round;
bool dosec = sec;
bool dofile = file;
@@ -2150,7 +2151,7 @@ static void u_undoredo(int undo, bool do_buf_event)
int new_flags;
fmark_T namedm[NMARKS];
visualinfo_T visualinfo;
- int empty_buffer; /* buffer became empty */
+ bool empty_buffer; // buffer became empty
u_header_T *curhead = curbuf->b_u_curhead;
/* Don't want autocommands using the undo structures here, they are
@@ -2217,7 +2218,7 @@ static void u_undoredo(int undo, bool do_buf_event)
}
}
- empty_buffer = FALSE;
+ empty_buffer = false;
/* delete the lines between top and bot and save them in newarray */
if (oldsize > 0) {
@@ -2228,9 +2229,10 @@ static void u_undoredo(int undo, bool do_buf_event)
newarray[i] = u_save_line(lnum);
/* remember we deleted the last line in the buffer, and a
* dummy empty line will be inserted */
- if (curbuf->b_ml.ml_line_count == 1)
- empty_buffer = TRUE;
- ml_delete(lnum, FALSE);
+ if (curbuf->b_ml.ml_line_count == 1) {
+ empty_buffer = true;
+ }
+ ml_delete(lnum, false);
}
} else
newarray = NULL;
@@ -2245,7 +2247,7 @@ static void u_undoredo(int undo, bool do_buf_event)
if (empty_buffer && lnum == 0) {
ml_replace((linenr_T)1, uep->ue_array[i], true);
} else {
- ml_append(lnum, uep->ue_array[i], (colnr_T)0, FALSE);
+ ml_append(lnum, uep->ue_array[i], (colnr_T)0, false);
}
xfree(uep->ue_array[i]);
}
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 767936ecee..3e0a5907be 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -277,7 +277,6 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
// Enums need a typecast to be used as array index (for Ultrix).
#define HL_ATTR(n) highlight_attr[(int)(n)]
-#define TERM_STR(n) term_strings[(int)(n)]
/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
/// character of up to 6 bytes, or one 16-bit character of up to three bytes
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 9d8cd21dba..6bc082ffb2 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -2295,8 +2295,7 @@ int win_close(win_T *win, bool free_buf)
EMSG(_("E813: Cannot close autocmd window"));
return FAIL;
}
- if ((firstwin == aucmd_win || lastwin_nofloating() == aucmd_win)
- && one_window()) {
+ if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) {
EMSG(_("E814: Cannot close window, only autocmd window would remain"));
return FAIL;
}
@@ -3413,16 +3412,18 @@ int win_alloc_first(void)
return OK;
}
-/*
- * Init "aucmd_win". This can only be done after the first
- * window is fully initialized, thus it can't be in win_alloc_first().
- */
+// Init `aucmd_win`. This can only be done after the first window
+// is fully initialized, thus it can't be in win_alloc_first().
void win_alloc_aucmd_win(void)
{
- aucmd_win = win_alloc(NULL, TRUE);
- win_init_some(aucmd_win, curwin);
+ Error err = ERROR_INIT;
+ FloatConfig fconfig = FLOAT_CONFIG_INIT;
+ fconfig.width = Columns;
+ fconfig.height = 5;
+ fconfig.focusable = false;
+ aucmd_win = win_new_float(NULL, fconfig, &err);
+ aucmd_win->w_buffer->b_nwindows--;
RESET_BINDING(aucmd_win);
- new_frame(aucmd_win);
}
/*
@@ -3793,6 +3794,9 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
* the frames for that. When the Vim window was resized need to update
* frame sizes too. Use the stored value of p_ch, so that it can be
* different for each tab page. */
+ if (p_ch != curtab->tp_ch_used) {
+ clear_cmdline = true;
+ }
p_ch = curtab->tp_ch_used;
if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow
))
@@ -3847,7 +3851,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab)
*/
void goto_tabpage(int n)
{
- tabpage_T *tp;
+ tabpage_T *tp = NULL; // shut up compiler
tabpage_T *ttp;
int i;
@@ -5959,16 +5963,40 @@ void check_lnums(int do_curwin)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if ((do_curwin || wp != curwin) && wp->w_buffer == curbuf) {
+ // save the original cursor position and topline
+ wp->w_save_cursor.w_cursor_save = wp->w_cursor;
+ wp->w_save_cursor.w_topline_save = wp->w_topline;
+
if (wp->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
wp->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
if (wp->w_topline > curbuf->b_ml.ml_line_count) {
wp->w_topline = curbuf->b_ml.ml_line_count;
}
+
+ // save the corrected cursor position and topline
+ wp->w_save_cursor.w_cursor_corr = wp->w_cursor;
+ wp->w_save_cursor.w_topline_corr = wp->w_topline;
}
}
}
+/// Reset cursor and topline to its stored values from check_lnums().
+/// check_lnums() must have been called first!
+void reset_lnums(void)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == curbuf) {
+ // Restore the value if the autocommand didn't change it.
+ if (equalpos(wp->w_save_cursor.w_cursor_corr, wp->w_cursor)) {
+ wp->w_cursor = wp->w_save_cursor.w_cursor_save;
+ }
+ if (wp->w_save_cursor.w_topline_corr == wp->w_topline) {
+ wp->w_topline = wp->w_save_cursor.w_topline_save;
+ }
+ }
+ }
+}
/*
* A snapshot of the window sizes, to restore them after closing the help