diff options
Diffstat (limited to 'src')
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(®match, 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(®match, linebuf, (colnr_T)0); + fmt_ptr->prog = regmatch.regprog; + if (r) { + status = qf_parse_match(linebuf, linelen, fmt_ptr, ®match, 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(®match, 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(®match, 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, ®match, 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(®match, line, col, false); + regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case }; + int r = vim_regexec_string(®match, 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 |