diff options
Diffstat (limited to 'src')
155 files changed, 11770 insertions, 5610 deletions
diff --git a/src/Doxyfile b/src/Doxyfile index de31c8355f..461fafe99d 100644 --- a/src/Doxyfile +++ b/src/Doxyfile @@ -243,7 +243,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # that for custom extensions you also need to set FILE_PATTERNS otherwise the # files are not read by doxygen. -EXTENSION_MAPPING = +EXTENSION_MAPPING = lua=C # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all # comments according to the Markdown format, which allows for more readable @@ -672,7 +672,7 @@ INPUT_ENCODING = UTF-8 # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl -FILE_PATTERNS = *.h *.c +FILE_PATTERNS = *.h *.c *.lua # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. @@ -758,7 +758,7 @@ INPUT_FILTER = # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. -FILTER_PATTERNS = +FILTER_PATTERNS = *.lua=scripts/lua2dox_filter # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source diff --git a/src/clint.py b/src/clint.py index 1ef31820ee..6643d8956a 100755 --- a/src/clint.py +++ b/src/clint.py @@ -3246,7 +3246,7 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, r'|li_(?:next|prev|tv))\b', line) if match: error(filename, linenum, 'runtime/deprecated', 4, - 'Accessing list_T internals directly is prohibited') + 'Accessing list_T internals directly is prohibited (hint: see commit d46e37cb4c71)') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 3613a8f8bc..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" @@ -49,7 +50,7 @@ /// Gets the buffer line count /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Line count, or 0 for unloaded buffer. |api-buffer| Integer nvim_buf_line_count(Buffer buffer, Error *err) @@ -97,15 +98,16 @@ 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 buffer The buffer handle +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer /// @param send_buffer Set to true if the initial notification should contain /// the whole buffer. If so, the first notification will be a /// `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, @@ -128,11 +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 buffer The buffer handle -/// @param[out] err Details of an error that may have occurred +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer +/// @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, @@ -221,7 +224,8 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer Buffer handle +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index /// @param end Last line index (exclusive) /// @param strict_indexing Whether out-of-bounds should be an error. @@ -290,7 +294,7 @@ end: /// newend = end + int(include_end) + int(end < 0) /// int(bool) = 1 if bool is true else 0 /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index /// @param end Last line index /// @param include_start True if the slice includes the `start` parameter @@ -324,7 +328,8 @@ void buffer_set_line_slice(Buffer buffer, /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer Buffer handle +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index /// @param end Last line index (exclusive) /// @param strict_indexing Whether out-of-bounds should be an error. @@ -470,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++) { @@ -491,7 +497,7 @@ end: /// Unlike |line2byte()|, throws error for out-of-bounds indexing. /// Returns -1 for unloaded buffer. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param index Line index /// @param[out] err Error details, if any /// @return Integer byte offset, or -1 for unloaded buffer. @@ -518,7 +524,7 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) /// Gets a buffer-scoped (b:) variable. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Variable name /// @param[out] err Error details, if any /// @return Variable value @@ -536,7 +542,7 @@ Object nvim_buf_get_var(Buffer buffer, String name, Error *err) /// Gets a changed tick of a buffer /// -/// @param[in] buffer Buffer handle. +/// @param[in] buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// /// @return `b:changedtick` value. @@ -555,7 +561,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// Gets a list of buffer-local |mapping| definitions. /// /// @param mode Mode short-name ("n", "i", "v", ...) -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key holds the associated buffer handle. @@ -571,9 +577,34 @@ 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. +/// @param buffer Buffer handle, or 0 for current buffer /// @param opts Optional parameters. Currently not used. /// @param[out] err Error details, if any. /// @@ -613,7 +644,7 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dictionary opts, Error *err) /// Sets a buffer-scoped (b:) variable /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Variable name /// @param value Variable value /// @param[out] err Error details, if any @@ -631,7 +662,7 @@ void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) /// Removes a buffer-scoped (b:) variable /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Variable name /// @param[out] err Error details, if any void nvim_buf_del_var(Buffer buffer, String name, Error *err) @@ -650,7 +681,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) /// /// @deprecated /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Variable name /// @param value Variable value /// @param[out] err Error details, if any @@ -673,7 +704,7 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) /// /// @deprecated /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Variable name /// @param[out] err Error details, if any /// @return Old value @@ -691,7 +722,7 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) /// Gets a buffer option value /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Option name /// @param[out] err Error details, if any /// @return Option value @@ -710,7 +741,8 @@ Object nvim_buf_get_option(Buffer buffer, String name, Error *err) /// Sets a buffer option value. Passing 'nil' as value deletes the option (only /// works if there's a global fallback) /// -/// @param buffer Buffer handle +/// @param channel_id +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Option name /// @param value Option value /// @param[out] err Error details, if any @@ -732,7 +764,7 @@ void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, /// @deprecated The buffer number now is equal to the object id, /// so there is no need to use this function. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Buffer number Integer nvim_buf_get_number(Buffer buffer, Error *err) @@ -751,7 +783,7 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) /// Gets the full file name for the buffer /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Buffer name String nvim_buf_get_name(Buffer buffer, Error *err) @@ -769,7 +801,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err) /// Sets the full file name for a buffer /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Buffer name /// @param[out] err Error details, if any void nvim_buf_set_name(Buffer buffer, String name, Error *err) @@ -801,7 +833,7 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) /// Checks if a buffer is valid and loaded. See |api-buffer| for more info /// about unloaded buffers. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @return true if the buffer is valid and loaded, false otherwise. Boolean nvim_buf_is_loaded(Buffer buffer) FUNC_API_SINCE(5) @@ -817,7 +849,7 @@ Boolean nvim_buf_is_loaded(Buffer buffer) /// @note Even if a buffer is valid it may have been unloaded. See |api-buffer| /// for more info about unloaded buffers. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @return true if the buffer is valid, false otherwise. Boolean nvim_buf_is_valid(Buffer buffer) FUNC_API_SINCE(1) @@ -849,7 +881,7 @@ void buffer_insert(Buffer buffer, /// Return a tuple (row,col) representing the position of the named mark /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param name Mark name /// @param[out] err Error details, if any /// @return (row, col) tuple @@ -914,7 +946,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// supported for backwards compatibility, new code should use /// |nvim_create_namespace| to create a new empty namespace. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id namespace to use or -1 for ungrouped highlight /// @param hl_group Name of the highlight group to use /// @param line Line to highlight (zero-indexed) @@ -964,7 +996,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, /// To clear the namespace in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace to clear, or -1 to clear all namespaces. /// @param line_start Start of range of lines to clear /// @param line_end End of range of lines to clear (exclusive) or -1 to clear @@ -997,7 +1029,7 @@ void nvim_buf_clear_namespace(Buffer buffer, /// /// @deprecated use |nvim_buf_clear_namespace|. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace to clear, or -1 to clear all. /// @param line_start Start of range of lines to clear /// @param line_end End of range of lines to clear (exclusive) or -1 to clear @@ -1031,7 +1063,7 @@ void nvim_buf_clear_highlight(Buffer buffer, /// As a shorthand, `ns_id = 0` can be used to create a new namespace for the /// virtual text, the allocated id is then returned. /// -/// @param buffer Buffer handle +/// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace to use or 0 to create a namespace, /// or -1 for a ungrouped annotation /// @param line Line to annotate with virtual text (zero-indexed) @@ -1101,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/defs.h b/src/nvim/api/private/defs.h index feca140547..978c55691b 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -29,14 +29,13 @@ typedef enum { } ErrorType; typedef enum { - kMessageTypeRequest, - kMessageTypeResponse, - kMessageTypeNotification + kMessageTypeUnknown = -1, + // Per msgpack-rpc spec. + kMessageTypeRequest = 0, + kMessageTypeResponse = 1, + kMessageTypeNotification = 2, } MessageType; -/// Used as the message ID of notifications. -#define NO_RESPONSE UINT64_MAX - /// Mask for all internal calls #define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1)) 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 d3cbb46dad..4f28ea5af3 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -29,11 +29,12 @@ typedef struct { uint64_t channel_id; Array buffer; - int hl_id; // current higlight for legacy put event - Integer cursor_row, cursor_col; // Intended visibule cursor position + int hl_id; // Current highlight for legacy put event. + Integer cursor_row, cursor_col; // Intended visible cursor position. // Position of legacy cursor, used both for drawing and visible user cursor. Integer client_row, client_col; + bool wildmenu_active; } UIData; static PMap(uint64_t) *connected_uis = NULL; @@ -75,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 @@ -94,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; @@ -146,6 +163,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, data->buffer = (Array)ARRAY_DICT_INIT; data->hl_id = 0; data->client_col = -1; + data->wildmenu_active = false; ui->data = data; pmap_put(uint64_t)(connected_uis, channel_id, ui); @@ -162,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 { @@ -213,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"); @@ -262,20 +295,22 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, /// /// On invalid grid handle, fails with error. /// +/// @param channel_id /// @param grid The handle of the grid to be changed. /// @param width The new requested width. /// @param height The new requested height. +/// @param[out] err Error details, if any void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, - Integer height, Error *error) + Integer height, Error *err) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, kErrorTypeException, + api_set_error(err, kErrorTypeException, "UI not attached to channel: %" PRId64, channel_id); return; } - ui_grid_resize((handle_T)grid, (int)width, (int)height, error); + ui_grid_resize((handle_T)grid, (int)width, (int)height, err); } /// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). @@ -586,6 +621,7 @@ static Array translate_firstarg(UI *ui, Array args) static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) { + UIData *data = ui->data; if (!ui->ui_ext[kUILinegrid]) { // the representation of highlights in cmdline changed, translate back // never consumes args @@ -611,6 +647,39 @@ static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) } } + // Back-compat: translate popupmenu_xx to legacy wildmenu_xx. + if (ui->ui_ext[kUIWildmenu]) { + if (strequal(name, "popupmenu_show")) { + data->wildmenu_active = (args.items[4].data.integer == -1) + || !ui->ui_ext[kUIPopupmenu]; + if (data->wildmenu_active) { + Array new_args = ARRAY_DICT_INIT; + Array items = args.items[0].data.array; + Array new_items = ARRAY_DICT_INIT; + for (size_t i = 0; i < items.size; i++) { + ADD(new_items, copy_object(items.items[i].data.array.items[0])); + } + ADD(new_args, ARRAY_OBJ(new_items)); + push_call(ui, "wildmenu_show", new_args); + if (args.items[1].data.integer != -1) { + Array new_args2 = ARRAY_DICT_INIT; + ADD(new_args2, args.items[1]); + push_call(ui, "wildmenu_select", new_args); + } + return; + } + } else if (strequal(name, "popupmenu_select")) { + if (data->wildmenu_active) { + name = "wildmenu_select"; + } + } else if (strequal(name, "popupmenu_hide")) { + if (data->wildmenu_active) { + name = "wildmenu_hide"; + } + } + } + + Array my_args = ARRAY_DICT_INIT; // Objects are currently single-reference // make a copy, but only if necessary diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index b89c5b6014..a1d25766fe 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -143,11 +143,11 @@ void cmdline_block_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_show(Array items) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; void wildmenu_select(Integer selected) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; void wildmenu_hide(void) - FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; void msg_show(String kind, Array content, Boolean replace_last) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index cb5ed5ecda..b8c863704a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -15,6 +15,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/buffer.h" +#include "nvim/api/window.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/lua/executor.h" @@ -213,8 +214,8 @@ Integer nvim_input(String keys) /// Send mouse event from GUI. /// -/// The call is non-blocking. It doesn't wait on any resulting action, but -/// queues the event to be processed soon by the event loop. +/// Non-blocking: does not wait on any result, but queues the event to be +/// processed soon by the event loop. /// /// @note Currently this doesn't support "scripting" multiple mouse events /// by calling it multiple times in a loop: the intermediate mouse @@ -232,6 +233,7 @@ Integer nvim_input(String keys) /// @param grid Grid number if the client uses |ui-multigrid|, else 0. /// @param row Mouse row-position (zero-based, like redraw events) /// @param col Mouse column-position (zero-based, like redraw events) +/// @param[out] err Error details, if any void nvim_input_mouse(String button, String action, String modifier, Integer grid, Integer row, Integer col, Error *err) FUNC_API_SINCE(6) FUNC_API_ASYNC @@ -681,14 +683,14 @@ void nvim_set_current_dir(String dir, Error *err) try_start(); - if (vim_chdir((char_u *)string, kCdScopeGlobal)) { + if (vim_chdir((char_u *)string)) { if (!try_end(err)) { api_set_error(err, kErrorTypeException, "Failed to change directory"); } return; } - post_chdir(kCdScopeGlobal); + post_chdir(kCdScopeGlobal, true); try_end(err); } @@ -755,9 +757,9 @@ void nvim_del_var(String name, Error *err) /// @deprecated /// @see nvim_set_var -/// @return Old value or nil if there was no previous value. /// @warning May return nil if there was no previous value /// OR if previous value was `v:null`. +/// @return Old value or nil if there was no previous value. Object vim_set_var(String name, Object value, Error *err) { return dict_set_var(&globvardict, name, value, false, true, err); @@ -805,6 +807,7 @@ Object nvim_get_option(String name, Error *err) /// Sets an option value. /// +/// @param channel_id /// @param name Option name /// @param value New option value /// @param[out] err Error details, if any @@ -937,6 +940,7 @@ Window nvim_get_current_win(void) /// Sets the current window. /// /// @param window Window handle +/// @param[out] err Error details, if any void nvim_set_current_win(Window window, Error *err) FUNC_API_SINCE(1) { @@ -958,7 +962,7 @@ void nvim_set_current_win(Window window, Error *err) /// Creates a new, empty, unnamed buffer. /// -/// @param listed Controls 'buflisted' +/// @param listed Sets 'buflisted' /// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work /// (always 'nomodified') /// @param[out] err Error details, if any @@ -997,38 +1001,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// GUI with the |ui-multigrid| extension. External windows are only supported /// with multigrid GUIs, and are displayed as separate top-level windows. /// -/// Exactly one of `external` and `relative` must be specified. +/// For a general overview of floats, see |api-floatwin|. /// -/// @param buffer handle of buffer to be displayed in the window -/// @param enter whether the window should be entered (made the current window) -/// @param width width of window (in character cells) -/// @param height height of window (in character cells) -/// @param options dict of options for configuring window positioning -/// accepts the following keys: -/// `relative`: If set, the window becomes a floating window. The window -/// will be placed with row,col coordinates relative one of the -/// following: -/// "editor" the global editor grid -/// "win" a window. Use 'win' option below to specify window id, -/// or current window will be used by default. -/// "cursor" the cursor position in current window. -/// `anchor`: the corner of the float that the row,col position defines -/// "NW" north-west (default) -/// "NE" north-east -/// "SW" south-west -/// "SE" south-east -/// `focusable`: Whether window can be focused by wincmds and -/// mouse events. Defaults to true. Even if set to false, the window -/// can still be entered using |nvim_set_current_win()| API call. -/// `row`: row position. Screen cell height are used as unit. Can be -/// floating point. -/// `col`: column position. Screen cell width is used as unit. Can be -/// floating point. -/// `win`: when using relative='win', window id of the window where the -/// position is defined. -/// `external` GUI should display the window as an external -/// top-level window. Currently accepts no other positioning options -/// together with this. +/// Exactly one of `external` and `relative` must be specified. The `width` and +/// `height` of the new window must be specified. /// /// With editor positioning row=0, col=0 refers to the top-left corner of the /// screen-grid and row=Lines-1, Columns-1 refers to the bottom-right corner. @@ -1042,27 +1018,55 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// could let floats hover outside of the main window like a tooltip, but /// this should not be used to specify arbitrary WM screen positions. /// +/// @param buffer handle of buffer to be displayed in the window +/// @param enter whether the window should be entered (made the current window) +/// @param config Dictionary for the window configuration accepts these keys: +/// - `relative`: If set, the window becomes a floating window. The window +/// will be placed with row,col coordinates relative to one of the +/// following: +/// - "editor" the global editor grid +/// - "win" a window. Use `win` to specify a window id, +/// or the current window will be used by default. +/// "cursor" the cursor position in current window. +/// - `win`: When using relative='win', window id of the window where the +/// position is defined. +/// - `anchor`: The corner of the float that the row,col position defines: +/// - "NW" north-west (default) +/// - "NE" north-east +/// - "SW" south-west +/// - "SE" south-east +/// - `height`: window height (in character cells). Minimum of 1. +/// - `width`: window width (in character cells). Minimum of 1. +/// - `row`: row position. Screen cell height are used as unit. Can be +/// floating point. +/// - `col`: column position. Screen cell width is used as unit. Can be +/// floating point. +/// - `focusable`: Whether window can be focused by wincmds and +/// mouse events. Defaults to true. Even if set to false, the window +/// can still be entered using |nvim_set_current_win()| API call. +/// - `external`: GUI should display the window as an external +/// top-level window. Currently accepts no other positioning +/// configuration together with this. /// @param[out] err Error details, if any -/// @return the window handle or 0 when error -Window nvim_open_win(Buffer buffer, Boolean enter, - Integer width, Integer height, - Dictionary options, Error *err) +/// +/// @return Window handle, or 0 on error +Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, + Error *err) FUNC_API_SINCE(6) { - win_T *old = curwin; - FloatConfig config = FLOAT_CONFIG_INIT; - if (!parse_float_config(options, &config, false, err)) { + FloatConfig fconfig = FLOAT_CONFIG_INIT; + if (!parse_float_config(config, &fconfig, false, err)) { return 0; } - win_T *wp = win_new_float(NULL, (int)width, (int)height, config, err); + win_T *wp = win_new_float(NULL, fconfig, err); if (!wp) { return 0; } - if (buffer > 0) { - nvim_set_current_buf(buffer, err); + if (enter) { + win_enter(wp, false); } - if (!enter) { - win_enter(old, false); + if (buffer > 0) { + nvim_win_set_buf(wp->handle, buffer, err); } return wp->handle; } @@ -1194,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) { @@ -1241,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. @@ -1272,47 +1336,48 @@ Array nvim_get_api_info(uint64_t channel_id) return rv; } -/// Identify the client for nvim. Can be called more than once, but subsequent -/// calls will remove earlier info, which should be resent if it is still -/// valid. (This could happen if a library first identifies the channel, and a +/// Identifies the client. Can be called more than once; subsequent calls +/// remove earlier info, which should be included by the caller if it is +/// still valid. (E.g. if a library first identifies the channel, then a /// plugin using that library later overrides that info) /// -/// @param name short name for the connected client -/// @param version Dictionary describing the version, with the following -/// possible keys (all optional) +/// @param channel_id +/// @param name Short name for the connected client +/// @param version Dictionary describing the version, with these +/// (optional) keys: /// - "major" major version (defaults to 0 if not set, for no release yet) /// - "minor" minor version /// - "patch" patch number /// - "prerelease" string describing a prerelease, like "dev" or "beta1" /// - "commit" hash or similar identifier of commit -/// @param type Must be one of the following values. A client library should -/// use "remote" if the library user hasn't specified other value. -/// - "remote" remote client that connected to nvim. +/// @param type Must be one of the following values. Client libraries should +/// default to "remote" unless overridden by the user. +/// - "remote" remote client connected to Nvim. /// - "ui" gui frontend -/// - "embedder" application using nvim as a component, for instance -/// IDE/editor implementing a vim mode. +/// - "embedder" application using Nvim as a component (for example, +/// IDE/editor implementing a vim mode). /// - "host" plugin host, typically started by nvim /// - "plugin" single plugin, started by nvim /// @param methods Builtin methods in the client. For a host, this does not /// include plugin methods which will be discovered later. /// The key should be the method name, the values are dicts with -/// the following (optional) keys: +/// these (optional) keys (more keys may be added in future +/// versions of Nvim, thus unknown keys are ignored. Clients +/// must only use keys defined in this or later versions of +/// Nvim): /// - "async" if true, send as a notification. If false or unspecified, /// use a blocking request /// - "nargs" Number of arguments. Could be a single integer or an array -/// two integers, minimum and maximum inclusive. -/// Further keys might be added in later versions of nvim and unknown keys -/// are thus ignored. Clients must only use keys defined in this or later -/// versions of nvim! -/// -/// @param attributes Informal attributes describing the client. Clients might -/// define their own keys, but the following are suggested: -/// - "website" Website of client (for instance github repository) -/// - "license" Informal description of the license, such as "Apache 2", -/// "GPLv3" or "MIT" -/// - "logo" URI or path to image, preferably small logo or icon. -/// .png or .svg format is preferred. +/// of two integers, minimum and maximum inclusive. +/// +/// @param attributes Arbitrary string:string map of informal client properties. +/// Suggested keys: +/// - "website": Client homepage URL (e.g. GitHub repository) +/// - "license": License description ("Apache 2", "GPLv3", "MIT", …) +/// - "logo": URI or path to image, preferably small logo or icon. +/// .png or .svg format is preferred. /// +/// @param[out] err Error details, if any void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, String type, Dictionary methods, Dictionary attributes, @@ -1344,15 +1409,14 @@ void nvim_set_client_info(uint64_t channel_id, String name, /// Get information about a channel. /// -/// @returns a Dictionary, describing a channel with the -/// following keys: -/// - "stream" the stream underlying the channel +/// @returns Dictionary describing a channel, with these keys: +/// - "stream" the stream underlying the channel /// - "stdio" stdin and stdout of this Nvim instance /// - "stderr" stderr of this Nvim instance /// - "socket" TCP/IP socket or named pipe /// - "job" job with communication over its stdio /// - "mode" how data received on the channel is interpreted -/// - "bytes" send and recieve raw bytes +/// - "bytes" send and receive raw bytes /// - "terminal" a |terminal| instance interprets ASCII sequences /// - "rpc" |RPC| communication on the channel is active /// - "pty" Name of pseudoterminal, if one is used (optional). @@ -1393,13 +1457,13 @@ Array nvim_list_chans(void) /// processing which have such side-effects, e.g. |:sleep| may wake timers). /// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. /// +/// @param channel_id /// @param calls an array of calls, where each call is described by an array -/// with two elements: the request name, and an array of arguments. -/// @param[out] err Details of a validation error of the nvim_multi_request call -/// itself, i.e. malformed `calls` parameter. Errors from called methods will -/// be indicated in the return value, see below. +/// with two elements: the request name, and an array of arguments. +/// @param[out] err Validation error details (malformed `calls` parameter), +/// if any. Errors from batched calls are given in the return value. /// -/// @return an array with two elements. The first is an array of return +/// @return Array of two elements. The first is an array of return /// values. The second is NIL if all calls succeeded. If a call resulted in /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an @@ -1490,9 +1554,8 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// Parse a VimL expression. /// -/// @param[in] expr Expression to parse. Is always treated as a single line. -/// @param[in] flags Flags: -/// +/// @param[in] expr Expression to parse. Always treated as a single line. +/// @param[in] flags Flags: /// - "m" if multiple expressions in a row are allowed (only /// the first one will be parsed), /// - "E" if EOC tokens are not allowed (determines whether @@ -1500,7 +1563,6 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// operator/space, though also yielding an error). /// - "l" when needing to start parsing with lvalues for /// ":let" or ":for". -/// /// Common flag sets: /// - "m" to parse like for ":echo". /// - "E" to parse like for "<C-r>=". @@ -1513,63 +1575,57 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// starting column and ending column (latter exclusive: /// one should highlight region [start_col, end_col)). /// -/// @return AST: top-level dictionary with these keys: -/// -/// "error": Dictionary with error, present only if parser saw some -/// error. Contains the following keys: -/// -/// "message": String, error message in printf format, translated. -/// Must contain exactly one "%.*s". -/// "arg": String, error message argument. -/// -/// "len": Amount of bytes successfully parsed. With flags equal to "" -/// that should be equal to the length of expr string. -/// -/// @note: “Sucessfully parsed†here means “participated in AST -/// creationâ€, not “till the first errorâ€. -/// -/// "ast": AST, either nil or a dictionary with these keys: -/// -/// "type": node type, one of the value names from ExprASTNodeType -/// stringified without "kExprNode" prefix. -/// "start": a pair [line, column] describing where node is “started†-/// where "line" is always 0 (will not be 0 if you will be -/// using nvim_parse_viml() on e.g. ":let", but that is not -/// present yet). Both elements are Integers. -/// "len": “length†of the node. This and "start" are there for -/// debugging purposes primary (debugging parser and providing -/// debug information). -/// "children": a list of nodes described in top/"ast". There always -/// is zero, one or two children, key will not be present -/// if node has no children. Maximum number of children -/// may be found in node_maxchildren array. -/// -/// Local values (present only for certain nodes): -/// -/// "scope": a single Integer, specifies scope for "Option" and -/// "PlainIdentifier" nodes. For "Option" it is one of -/// ExprOptScope values, for "PlainIdentifier" it is one of -/// ExprVarScope values. -/// "ident": identifier (without scope, if any), present for "Option", -/// "PlainIdentifier", "PlainKey" and "Environment" nodes. -/// "name": Integer, register name (one character) or -1. Only present -/// for "Register" nodes. -/// "cmp_type": String, comparison type, one of the value names from -/// ExprComparisonType, stringified without "kExprCmp" -/// prefix. Only present for "Comparison" nodes. -/// "ccs_strategy": String, case comparison strategy, one of the -/// value names from ExprCaseCompareStrategy, -/// stringified without "kCCStrategy" prefix. Only -/// present for "Comparison" nodes. -/// "augmentation": String, augmentation type for "Assignment" nodes. -/// Is either an empty string, "Add", "Subtract" or -/// "Concat" for "=", "+=", "-=" or ".=" respectively. -/// "invert": Boolean, true if result of comparison needs to be -/// inverted. Only present for "Comparison" nodes. -/// "ivalue": Integer, integer value for "Integer" nodes. -/// "fvalue": Float, floating-point value for "Float" nodes. -/// "svalue": String, value for "SingleQuotedString" and -/// "DoubleQuotedString" nodes. +/// @return +/// - AST: top-level dictionary with these keys: +/// - "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// - "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// - "arg": String, error message argument. +/// - "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// (“Sucessfully parsed†here means “participated in AST +/// creationâ€, not “till the first errorâ€.) +/// - "ast": AST, either nil or a dictionary with these keys: +/// - "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// - "start": a pair [line, column] describing where node is "started" +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// - "len": “length†of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// - "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. +/// - Local values (present only for certain nodes): +/// - "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// - "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// - "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// - "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// - "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// - "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. +/// - "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. +/// - "ivalue": Integer, integer value for "Integer" nodes. +/// - "fvalue": Float, floating-point value for "Float" nodes. +/// - "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +/// @param[out] err Error details, if any Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) FUNC_API_SINCE(4) FUNC_API_ASYNC @@ -2034,15 +2090,12 @@ Dictionary nvim__stats(void) /// Gets a list of dictionaries representing attached UIs. /// -/// @return Array of UI dictionaries -/// -/// Each dictionary has the following keys: -/// - "height" requested height of the UI -/// - "width" requested width of the UI -/// - "rgb" whether the UI uses rgb colors (false implies cterm colors) -/// - "ext_..." Requested UI extensions, see |ui-options| -/// - "chan" Channel id of remote UI (not present for TUI) -/// +/// @return Array of UI dictionaries, each with these keys: +/// - "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-option| +/// - "chan" Channel id of remote UI (not present for TUI) Array nvim_list_uis(void) FUNC_API_SINCE(4) { @@ -2145,6 +2198,7 @@ Object nvim_get_proc(Integer pid, Error *err) /// @param finish Finish the completion and dismiss the popupmenu. Implies /// `insert`. /// @param opts Optional parameters. Reserved for future use. +/// @param[out] err Error details, if any void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts, Error *err) FUNC_API_SINCE(6) diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 157f73c9fa..e1c50cb89d 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -67,6 +67,10 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) buffer); } + // If window is not current, state logic will not validate its cursor. + // So do it now. + validate_cursor(); + restore_win(save_curwin, save_curtab, false); } @@ -345,6 +349,7 @@ Object nvim_win_get_option(Window window, String name, Error *err) /// Sets a window option value. Passing 'nil' as value deletes the option(only /// works if there's a global fallback) /// +/// @param channel_id /// @param window Window handle /// @param name Option name /// @param value Option value @@ -438,14 +443,16 @@ Boolean nvim_win_is_valid(Window window) /// floating and external windows (including changing a split window to these /// types). /// -/// See documentation at |nvim_open_win()|, for the meaning of parameters. Pass -/// in -1 for 'witdh' and 'height' to keep exiting size. +/// See documentation at |nvim_open_win()|, for the meaning of parameters. /// /// When reconfiguring a floating window, absent option keys will not be /// changed. The following restriction apply: `row`, `col` and `relative` /// must be reconfigured together. Only changing a subset of these is an error. -void nvim_win_config(Window window, Integer width, Integer height, - Dictionary options, Error *err) +/// +/// @param window Window handle +/// @param config Dictionary of window configuration +/// @param[out] err Error details, if any +void nvim_win_set_config(Window window, Dictionary config, Error *err) FUNC_API_SINCE(6) { win_T *win = find_window_by_handle(window, err); @@ -453,25 +460,67 @@ void nvim_win_config(Window window, Integer width, Integer height, return; } bool new_float = !win->w_floating; - width = width > 0 ? width: win->w_width; - height = height > 0 ? height : win->w_height; // reuse old values, if not overriden - FloatConfig config = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; + FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; - if (!parse_float_config(options, &config, !new_float, err)) { + if (!parse_float_config(config, &fconfig, !new_float, err)) { return; } if (new_float) { - if (!win_new_float(win, (int)width, (int)height, config, err)) { + if (!win_new_float(win, fconfig, err)) { return; } redraw_later(NOT_VALID); } else { - win_config_float(win, (int)width, (int)height, config); + win_config_float(win, fconfig); win->w_pos_changed = true; } } +/// Return window configuration. +/// +/// Return a dictionary containing the same config that can be given to +/// |nvim_open_win()|. +/// +/// `relative` will be an empty string for normal windows. +/// +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Window configuration +Dictionary nvim_win_get_config(Window window, Error *err) + FUNC_API_SINCE(6) +{ + Dictionary rv = ARRAY_DICT_INIT; + + win_T *wp = find_window_by_handle(window, err); + if (!wp) { + return rv; + } + + PUT(rv, "focusable", BOOLEAN_OBJ(wp->w_float_config.focusable)); + PUT(rv, "external", BOOLEAN_OBJ(wp->w_float_config.external)); + + if (wp->w_floating) { + PUT(rv, "width", INTEGER_OBJ(wp->w_float_config.width)); + PUT(rv, "height", INTEGER_OBJ(wp->w_float_config.height)); + if (!wp->w_float_config.external) { + if (wp->w_float_config.relative == kFloatRelativeWindow) { + PUT(rv, "win", INTEGER_OBJ(wp->w_float_config.window)); + } + PUT(rv, "anchor", STRING_OBJ(cstr_to_string( + float_anchor_str[wp->w_float_config.anchor]))); + PUT(rv, "row", FLOAT_OBJ(wp->w_float_config.row)); + PUT(rv, "col", FLOAT_OBJ(wp->w_float_config.col)); + } + } + + const char *rel = (wp->w_floating && !wp->w_float_config.external + ? float_relative_str[wp->w_float_config.relative] : ""); + PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel))); + + return rv; +} + /// Close a window. /// /// This is equivalent to |:close| with count except that it takes a window id. @@ -480,9 +529,7 @@ void nvim_win_config(Window window, Integer width, Integer height, /// @param force Behave like `:close!` The last window of a buffer with /// unwritten changes can be closed. The buffer will become /// hidden, even if 'hidden' is not set. -/// /// @param[out] err Error details, if any -/// @return Window number void nvim_win_close(Window window, Boolean force, Error *err) FUNC_API_SINCE(6) { diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index cc0ed0f587..ef528f72b8 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -29,6 +29,7 @@ return { 'CmdWinLeave', -- before leaving the cmdline window 'ColorScheme', -- after loading a colorscheme 'ColorSchemePre', -- before loading a colorscheme + 'CompleteChanged', -- after popup menu changed 'CompleteDone', -- after finishing insert complete 'CursorHold', -- cursor in same position for a while 'CursorHoldI', -- idem, in Insert mode @@ -64,7 +65,6 @@ return { 'InsertCharPre', -- before inserting a char 'InsertEnter', -- when entering Insert mode 'InsertLeave', -- when leaving Insert mode - 'JobActivity', -- when job sent some data 'MenuPopup', -- just before popup menu is displayed 'OptionSet', -- after setting any option 'QuickFixCmdPost', -- after :make, :grep etc. @@ -88,7 +88,7 @@ return { 'TabNew', -- when creating a new tab 'TabNewEntered', -- after entering a new tab 'TermChanged', -- after changing 'term' - 'TermClose', -- after the processs exits + 'TermClose', -- after the process exits 'TermOpen', -- after opening a terminal buffer 'TermResponse', -- after setting "v:termresponse" 'TextChanged', -- text was modified diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 8cb4e32815..cdb226b94d 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1,23 +1,23 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * buffer.c: functions for dealing with the buffer structure - */ +// +// buffer.c: functions for dealing with the buffer structure +// -/* - * The buffer list is a double linked list of all buffers. - * Each buffer can be in one of these states: - * never loaded: BF_NEVERLOADED is set, only the file name is valid - * not loaded: b_ml.ml_mfp == NULL, no memfile allocated - * hidden: b_nwindows == 0, loaded but not displayed in a window - * normal: loaded and displayed in a window - * - * Instead of storing file names all over the place, each file name is - * stored in the buffer list. It can be referenced by a number. - * - * The current implementation remembers all file names ever used. - */ +// +// The buffer list is a double linked list of all buffers. +// Each buffer can be in one of these states: +// never loaded: BF_NEVERLOADED is set, only the file name is valid +// not loaded: b_ml.ml_mfp == NULL, no memfile allocated +// hidden: b_nwindows == 0, loaded but not displayed in a window +// normal: loaded and displayed in a window +// +// Instead of storing file names all over the place, each file name is +// stored in the buffer list. It can be referenced by a number. +// +// The current implementation remembers all file names ever used. +// #include <stdbool.h> #include <string.h> @@ -145,16 +145,13 @@ read_buffer( return retval; } -/* - * Open current buffer, that is: open the memfile and read the file into - * memory. - * Return FAIL for failure, OK otherwise. - */ -int -open_buffer ( - int read_stdin, /* read file from stdin */ - exarg_T *eap, /* for forced 'ff' and 'fenc' or NULL */ - int flags /* extra flags for readfile() */ +// Open current buffer, that is: open the memfile and read the file into +// memory. +// Return FAIL for failure, OK otherwise. +int open_buffer( + int read_stdin, // read file from stdin + exarg_T *eap, // for forced 'ff' and 'fenc' or NULL + int flags // extra flags for readfile() ) { int retval = OK; @@ -169,14 +166,14 @@ open_buffer ( */ if (readonlymode && curbuf->b_ffname != NULL && (curbuf->b_flags & BF_NEVERLOADED)) - curbuf->b_p_ro = TRUE; + curbuf->b_p_ro = true; if (ml_open(curbuf) == FAIL) { /* * There MUST be a memfile, otherwise we can't do anything * If we can't create one for the current buffer, take another buffer */ - close_buffer(NULL, curbuf, 0, FALSE); + close_buffer(NULL, curbuf, 0, false); curbuf = NULL; FOR_ALL_BUFFERS(buf) { @@ -196,8 +193,9 @@ open_buffer ( } EMSG(_("E83: Cannot allocate buffer, using other one...")); enter_buffer(curbuf); - if (old_tw != curbuf->b_p_tw) + if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); + } return FAIL; } @@ -206,7 +204,7 @@ open_buffer ( set_bufref(&old_curbuf, curbuf); modified_was_set = false; - /* mark cursor position as being invalid */ + // mark cursor position as being invalid curwin->w_valid = 0; if (curbuf->b_ffname != NULL) { @@ -265,7 +263,7 @@ open_buffer ( * it possible to retry when 'fileformat' or 'fileencoding' was * guessed wrong. */ - curbuf->b_p_bin = TRUE; + curbuf->b_p_bin = true; retval = readfile(NULL, NULL, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, NULL, flags | (READ_NEW + READ_STDIN)); @@ -275,7 +273,7 @@ open_buffer ( } } - /* if first time loading this buffer, init b_chartab[] */ + // if first time loading this buffer, init b_chartab[] if (curbuf->b_flags & BF_NEVERLOADED) { (void)buf_init_chartab(curbuf, false); parse_cino(curbuf); @@ -302,20 +300,21 @@ open_buffer ( curbuf->b_last_changedtick = buf_get_changedtick(curbuf); curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf); - /* require "!" to overwrite the file, because it wasn't read completely */ - if (aborting()) + // require "!" to overwrite the file, because it wasn't read completely + if (aborting()) { curbuf->b_flags |= BF_READERR; + } /* Need to update automatic folding. Do this before the autocommands, * they may use the fold info. */ foldUpdateAll(curwin); - /* need to set w_topline, unless some autocommand already did that. */ + // need to set w_topline, unless some autocommand already did that. if (!(curwin->w_valid & VALID_TOPLINE)) { curwin->w_topline = 1; curwin->w_topfill = 0; } - apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval); + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); if (retval == FAIL) { return FAIL; @@ -333,10 +332,10 @@ open_buffer ( do_modelines(0); curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); - apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf, - &retval); + apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, + &retval); - /* restore curwin/curbuf and a few other things */ + // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); } @@ -457,14 +456,14 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) set_last_cursor(win); } buflist_setfpos(buf, win, - win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum, - win->w_cursor.col, TRUE); + win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum, + win->w_cursor.col, true); } bufref_T bufref; set_bufref(&bufref, buf); - /* When the buffer is no longer in a window, trigger BufWinLeave */ + // When the buffer is no longer in a window, trigger BufWinLeave if (buf->b_nwindows == 1) { buf->b_locked++; if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, false, @@ -497,8 +496,9 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) return; } } - if (aborting()) /* autocmds may abort script processing */ + if (aborting()) { // autocmds may abort script processing return; + } } // If the buffer was in curwin and the window has changed, go back to that @@ -531,9 +531,10 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) terminal_close(buf->terminal, NULL); } - /* Always remove the buffer when there is no file name. */ - if (buf->b_ffname == NULL) - del_buf = TRUE; + // Always remove the buffer when there is no file name. + if (buf->b_ffname == NULL) { + del_buf = true; + } /* * Free all things allocated for this buffer. @@ -573,8 +574,9 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) * obtained anyway. Therefore only return if curbuf changed to the * deleted buffer. */ - if (buf == curbuf && !is_curbuf) + if (buf == curbuf && !is_curbuf) { return; + } if (win != NULL // Avoid bogus clang warning. && win_valid_any_tab(win) @@ -588,7 +590,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) buf->b_nwindows--; } - /* Change directories when the 'acd' option is set. */ + // Change directories when the 'acd' option is set. do_autochdir(); // Disable buffer-updates for the current buffer. @@ -601,30 +603,33 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) if (wipe_buf) { xfree(buf->b_ffname); xfree(buf->b_sfname); - if (buf->b_prev == NULL) + if (buf->b_prev == NULL) { firstbuf = buf->b_next; - else + } else { buf->b_prev->b_next = buf->b_next; - if (buf->b_next == NULL) + } + if (buf->b_next == NULL) { lastbuf = buf->b_prev; - else + } else { buf->b_next->b_prev = buf->b_prev; + } free_buffer(buf); } else { if (del_buf) { /* Free all internal variables and reset option values, to make * ":bdel" compatible with Vim 5.7. */ - free_buffer_stuff(buf, TRUE); + free_buffer_stuff(buf, true); - /* Make it look like a new buffer. */ + // Make it look like a new buffer. buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; - /* Init the options when loaded again. */ + // Init the options when loaded again. buf->b_p_initialized = false; } buf_clear_file(buf); - if (del_buf) - buf->b_p_bl = FALSE; + if (del_buf) { + buf->b_p_bl = false; + } } } @@ -634,13 +639,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) void buf_clear_file(buf_T *buf) { buf->b_ml.ml_line_count = 1; - unchanged(buf, TRUE); - buf->b_p_eol = TRUE; - buf->b_start_eol = TRUE; - buf->b_p_bomb = FALSE; - buf->b_start_bomb = FALSE; + unchanged(buf, true); + buf->b_p_eol = true; + buf->b_start_eol = true; + buf->b_p_bomb = false; + buf->b_start_bomb = false; buf->b_ml.ml_mfp = NULL; - buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */ + buf->b_ml.ml_flags = ML_EMPTY; // empty buffer } /// Clears the current buffer contents. @@ -714,29 +719,30 @@ void buf_freeall(buf_T *buf, int flags) * it's OK to delete the curbuf, because a new one is obtained anyway. * Therefore only return if curbuf changed to the deleted buffer. */ - if (buf == curbuf && !is_curbuf) + if (buf == curbuf && !is_curbuf) { return; + } diff_buf_delete(buf); // Can't use 'diff' for unloaded buffer. // Remove any ownsyntax, unless exiting. if (curwin != NULL && curwin->w_buffer == buf) { reset_synblock(curwin); } - /* No folds in an empty buffer. */ + // No folds in an empty buffer. FOR_ALL_TAB_WINDOWS(tp, win) { if (win->w_buffer == buf) { clearFolding(win); } } - ml_close(buf, TRUE); /* close and delete the memline/memfile */ - buf->b_ml.ml_line_count = 0; /* no lines in buffer */ + ml_close(buf, true); // close and delete the memline/memfile + buf->b_ml.ml_line_count = 0; // no lines in buffer if ((flags & BFA_KEEP_UNDO) == 0) { - u_blockfree(buf); /* free the memory allocated for undo */ - u_clearall(buf); /* reset all undo information */ + u_blockfree(buf); // free the memory allocated for undo + u_clearall(buf); // reset all undo information } - syntax_clear(&buf->b_s); /* reset syntax info */ - buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */ + syntax_clear(&buf->b_s); // reset syntax info + buf->b_flags &= ~BF_READERR; // a read error is no longer relevant } /* @@ -775,10 +781,10 @@ static void free_buffer(buf_T *buf) /* * Free stuff in the buffer for ":bdel" and when wiping out the buffer. */ -static void -free_buffer_stuff ( +static void +free_buffer_stuff( buf_T *buf, - int free_options /* free options as well */ + int free_options // free options as well ) { if (free_options) { @@ -842,14 +848,14 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count) if (swap_exists_action == SEA_QUIT && *eap->cmd == 's') { cleanup_T cs; - /* Reset the error/interrupt/exception state here so that - * aborting() returns FALSE when closing a window. */ + // Reset the error/interrupt/exception state here so that + // aborting() returns false when closing a window. enter_cleanup(&cs); // Quitting means closing the split window, nothing else. win_close(curwin, true); swap_exists_action = SEA_NONE; - swap_exists_did_quit = TRUE; + swap_exists_did_quit = true; /* Restore the error/interrupt/exception state if not discarded by a * new aborting error, interrupt, or uncaught exception. */ @@ -871,8 +877,8 @@ void handle_swap_exists(bufref_T *old_curbuf) buf_T *buf; if (swap_exists_action == SEA_QUIT) { - /* Reset the error/interrupt/exception state here so that - * aborting() returns FALSE when closing a buffer. */ + // Reset the error/interrupt/exception state here so that + // aborting() returns false when closing a buffer. enter_cleanup(&cs); // User selected Quit at ATTENTION prompt. Go back to previous @@ -902,20 +908,20 @@ void handle_swap_exists(bufref_T *old_curbuf) check_colorcolumn(curwin); } } - /* If "old_curbuf" is NULL we are in big trouble here... */ + // If "old_curbuf" is NULL we are in big trouble here... /* Restore the error/interrupt/exception state if not discarded by a * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } else if (swap_exists_action == SEA_RECOVER) { - /* Reset the error/interrupt/exception state here so that - * aborting() returns FALSE when closing a buffer. */ + // Reset the error/interrupt/exception state here so that + // aborting() returns false when closing a buffer. enter_cleanup(&cs); - /* User selected Recover at ATTENTION prompt. */ - msg_scroll = TRUE; + // User selected Recover at ATTENTION prompt. + msg_scroll = true; ml_recover(); - MSG_PUTS("\n"); /* don't overwrite the last message */ + MSG_PUTS("\n"); // don't overwrite the last message cmdline_row = msg_row; do_modelines(0); @@ -940,30 +946,32 @@ void handle_swap_exists(bufref_T *old_curbuf) * Returns error message or NULL */ char_u * -do_bufdel ( +do_bufdel( int command, - char_u *arg, /* pointer to extra arguments */ + char_u *arg, // pointer to extra arguments int addr_count, - int start_bnr, /* first buffer number in a range */ - int end_bnr, /* buffer nr or last buffer nr in a range */ + int start_bnr, // first buffer number in a range + int end_bnr, // buffer nr or last buffer nr in a range int forceit ) { - int do_current = 0; /* delete current buffer? */ - int deleted = 0; /* number of buffers deleted */ - char_u *errormsg = NULL; /* return value */ - int bnr; /* buffer number */ + int do_current = 0; // delete current buffer? + int deleted = 0; // number of buffers deleted + char_u *errormsg = NULL; // return value + int bnr; // buffer number char_u *p; if (addr_count == 0) { (void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit); } else { if (addr_count == 2) { - if (*arg) /* both range and argument is not allowed */ + if (*arg) { // both range and argument is not allowed return (char_u *)_(e_trailing); + } bnr = start_bnr; - } else /* addr_count == 1 */ + } else { // addr_count == 1 bnr = end_bnr; + } for (; !got_int; os_breakcheck()) { /* @@ -972,61 +980,71 @@ do_bufdel ( * the current one and will be loaded, which may then * also be deleted, etc. */ - if (bnr == curbuf->b_fnum) + if (bnr == curbuf->b_fnum) { do_current = bnr; - else if (do_buffer(command, DOBUF_FIRST, FORWARD, bnr, - forceit) == OK) - ++deleted; + } else if (do_buffer(command, DOBUF_FIRST, FORWARD, bnr, + forceit) == OK) { + deleted++; + } /* * find next buffer number to delete/unload */ if (addr_count == 2) { - if (++bnr > end_bnr) + if (++bnr > end_bnr) { break; - } else { /* addr_count == 1 */ + } + } else { // addr_count == 1 arg = skipwhite(arg); - if (*arg == NUL) + if (*arg == NUL) { break; + } if (!ascii_isdigit(*arg)) { p = skiptowhite_esc(arg); bnr = buflist_findpat(arg, p, command == DOBUF_WIPE, - FALSE, FALSE); - if (bnr < 0) /* failed */ + false, false); + if (bnr < 0) { // failed break; + } arg = p; } else bnr = getdigits_int(&arg); } } - if (!got_int && do_current && do_buffer(command, DOBUF_FIRST, - FORWARD, do_current, forceit) == OK) - ++deleted; + if (!got_int && do_current + && do_buffer(command, DOBUF_FIRST, + FORWARD, do_current, forceit) == OK) { + deleted++; + } if (deleted == 0) { - if (command == DOBUF_UNLOAD) + if (command == DOBUF_UNLOAD) { STRCPY(IObuff, _("E515: No buffers were unloaded")); - else if (command == DOBUF_DEL) + } else if (command == DOBUF_DEL) { STRCPY(IObuff, _("E516: No buffers were deleted")); - else + } else { STRCPY(IObuff, _("E517: No buffers were wiped out")); + } errormsg = IObuff; } else if (deleted >= p_report) { if (command == DOBUF_UNLOAD) { - if (deleted == 1) + if (deleted == 1) { MSG(_("1 buffer unloaded")); - else + } else { smsg(_("%d buffers unloaded"), deleted); + } } else if (command == DOBUF_DEL) { - if (deleted == 1) + if (deleted == 1) { MSG(_("1 buffer deleted")); - else + } else { smsg(_("%d buffers deleted"), deleted); + } } else { - if (deleted == 1) + if (deleted == 1) { MSG(_("1 buffer wiped out")); - else + } else { smsg(_("%d buffers wiped out"), deleted); + } } } } @@ -1055,8 +1073,8 @@ static int empty_curbuf(int close_others, int forceit, int action) set_bufref(&bufref, buf); if (close_others) { - /* Close any other windows on this buffer, then make it empty. */ - close_windows(buf, TRUE); + // Close any other windows on this buffer, then make it empty. + close_windows(buf, true); } setpcmark(); @@ -1092,13 +1110,13 @@ static int empty_curbuf(int close_others, int forceit, int action) * * Return FAIL or OK. */ -int -do_buffer ( +int +do_buffer( int action, int start, - int dir, /* FORWARD or BACKWARD */ - int count, /* buffer number or number of buffers */ - int forceit /* TRUE for :...! */ + int dir, // FORWARD or BACKWARD + int count, // buffer number or number of buffers + int forceit // true for :...! ) { buf_T *buf; @@ -1111,51 +1129,56 @@ do_buffer ( case DOBUF_LAST: buf = lastbuf; break; default: buf = curbuf; break; } - if (start == DOBUF_MOD) { /* find next modified buffer */ + if (start == DOBUF_MOD) { // find next modified buffer while (count-- > 0) { do { buf = buf->b_next; - if (buf == NULL) + if (buf == NULL) { buf = firstbuf; + } } while (buf != curbuf && !bufIsChanged(buf)); } if (!bufIsChanged(buf)) { EMSG(_("E84: No modified buffer found")); return FAIL; } - } else if (start == DOBUF_FIRST && count) { /* find specified buffer number */ - while (buf != NULL && buf->b_fnum != count) + } else if (start == DOBUF_FIRST && count) { // find specified buffer number + while (buf != NULL && buf->b_fnum != count) { buf = buf->b_next; + } } else { bp = NULL; while (count > 0 || (!unload && !buf->b_p_bl && bp != buf)) { /* remember the buffer where we start, we come back there when all * buffers are unlisted. */ - if (bp == NULL) + if (bp == NULL) { bp = buf; + } if (dir == FORWARD) { buf = buf->b_next; - if (buf == NULL) + if (buf == NULL) { buf = firstbuf; + } } else { buf = buf->b_prev; - if (buf == NULL) + if (buf == NULL) { buf = lastbuf; + } } - /* don't count unlisted buffers */ + // don't count unlisted buffers if (unload || buf->b_p_bl) { - --count; - bp = NULL; /* use this buffer as new starting point */ + count--; + bp = NULL; // use this buffer as new starting point } if (bp == buf) { - /* back where we started, didn't find anything. */ + // back where we started, didn't find anything. EMSG(_("E85: There is no listed buffer")); return FAIL; } } } - if (buf == NULL) { /* could not find it */ + if (buf == NULL) { // could not find it if (start == DOBUF_FIRST) { // don't warn when deleting if (!unload) { @@ -1180,8 +1203,9 @@ do_buffer ( /* When unloading or deleting a buffer that's already unloaded and * unlisted: fail silently. */ - if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl) + if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl) { return FAIL; + } if (!forceit && (buf->terminal || bufIsChanged(buf))) { if ((p_confirm || cmdmod.confirm) && p_write && !buf->terminal) { @@ -1231,8 +1255,9 @@ do_buffer ( break; } } - if (bp == NULL && buf == curbuf) - return empty_curbuf(TRUE, forceit, action); + if (bp == NULL && buf == curbuf) { + return empty_curbuf(true, forceit, action); + } /* * If the deleted buffer is the current one, close the current window @@ -1242,8 +1267,9 @@ do_buffer ( while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) { - if (win_close(curwin, false) == FAIL) + if (win_close(curwin, false) == FAIL) { break; + } } /* @@ -1273,61 +1299,72 @@ do_buffer ( int jumpidx; jumpidx = curwin->w_jumplistidx - 1; - if (jumpidx < 0) + if (jumpidx < 0) { jumpidx = curwin->w_jumplistlen - 1; + } forward = jumpidx; while (jumpidx != curwin->w_jumplistidx) { buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); if (buf != NULL) { - if (buf == curbuf || !buf->b_p_bl) - buf = NULL; /* skip current and unlisted bufs */ - else if (buf->b_ml.ml_mfp == NULL) { - /* skip unloaded buf, but may keep it for later */ - if (bp == NULL) + if (buf == curbuf || !buf->b_p_bl) { + buf = NULL; // skip current and unlisted bufs + } else if (buf->b_ml.ml_mfp == NULL) { + // skip unloaded buf, but may keep it for later + if (bp == NULL) { bp = buf; + } buf = NULL; } } - if (buf != NULL) /* found a valid buffer: stop searching */ + if (buf != NULL) { // found a valid buffer: stop searching break; - /* advance to older entry in jump list */ - if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) + } + // advance to older entry in jump list + if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) { break; - if (--jumpidx < 0) + } + if (--jumpidx < 0) { jumpidx = curwin->w_jumplistlen - 1; - if (jumpidx == forward) /* List exhausted for sure */ + } + if (jumpidx == forward) { // List exhausted for sure break; + } } } - if (buf == NULL) { /* No previous buffer, Try 2'nd approach */ - forward = TRUE; + if (buf == NULL) { // No previous buffer, Try 2'nd approach + forward = true; buf = curbuf->b_next; for (;; ) { if (buf == NULL) { - if (!forward) /* tried both directions */ + if (!forward) { // tried both directions break; + } buf = curbuf->b_prev; - forward = FALSE; + forward = false; continue; } - /* in non-help buffer, try to skip help buffers, and vv */ + // in non-help buffer, try to skip help buffers, and vv if (buf->b_help == curbuf->b_help && buf->b_p_bl) { - if (buf->b_ml.ml_mfp != NULL) /* found loaded buffer */ + if (buf->b_ml.ml_mfp != NULL) { // found loaded buffer break; - if (bp == NULL) /* remember unloaded buf for later */ + } + if (bp == NULL) { // remember unloaded buf for later bp = buf; + } } - if (forward) + if (forward) { buf = buf->b_next; - else + } else { buf = buf->b_prev; + } } } - if (buf == NULL) /* No loaded buffer, use unloaded one */ + if (buf == NULL) { // No loaded buffer, use unloaded one buf = bp; - if (buf == NULL) { /* No loaded buffer, find listed one */ + } + if (buf == NULL) { // No loaded buffer, find listed one FOR_ALL_BUFFERS(buf2) { if (buf2->b_p_bl && buf2 != curbuf) { buf = buf2; @@ -1335,39 +1372,44 @@ do_buffer ( } } } - if (buf == NULL) { /* Still no buffer, just take one */ - if (curbuf->b_next != NULL) + if (buf == NULL) { // Still no buffer, just take one + if (curbuf->b_next != NULL) { buf = curbuf->b_next; - else + } else { buf = curbuf->b_prev; + } } } if (buf == NULL) { /* Autocommands must have wiped out all other buffers. Only option * now is to make the current buffer empty. */ - return empty_curbuf(FALSE, forceit, action); + return empty_curbuf(false, forceit, action); } /* * make buf current buffer */ - if (action == DOBUF_SPLIT) { /* split window first */ - /* If 'switchbuf' contains "useopen": jump to first window containing - * "buf" if one exists */ - if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) + if (action == DOBUF_SPLIT) { // split window first + // If 'switchbuf' contains "useopen": jump to first window containing + // "buf" if one exists + if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) { return OK; - /* If 'switchbuf' contains "usetab": jump to first window in any tab - * page containing "buf" if one exists */ - if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) + } + // If 'switchbuf' contains "usetab": jump to first window in any tab + // page containing "buf" if one exists + if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) { return OK; - if (win_split(0, 0) == FAIL) + } + if (win_split(0, 0) == FAIL) { return FAIL; + } } - /* go to current buffer - nothing to do */ - if (buf == curbuf) + // go to current buffer - nothing to do + if (buf == curbuf) { return OK; + } /* * Check if the current buffer may be abandoned. @@ -1388,15 +1430,16 @@ do_buffer ( } } - /* Go to the other buffer. */ + // Go to the other buffer. set_curbuf(buf, action); if (action == DOBUF_SPLIT) { - RESET_BINDING(curwin); /* reset 'scrollbind' and 'cursorbind' */ + RESET_BINDING(curwin); // reset 'scrollbind' and 'cursorbind' } - if (aborting()) /* autocmds may abort script processing */ + if (aborting()) { // autocmds may abort script processing return FAIL; + } return OK; } @@ -1419,12 +1462,13 @@ void set_curbuf(buf_T *buf, int action) long old_tw = curbuf->b_p_tw; setpcmark(); - if (!cmdmod.keepalt) - curwin->w_alt_fnum = curbuf->b_fnum; /* remember alternate file */ - buflist_altfpos(curwin); /* remember curpos */ + if (!cmdmod.keepalt) { + curwin->w_alt_fnum = curbuf->b_fnum; // remember alternate file + } + buflist_altfpos(curwin); // remember curpos - /* Don't restart Select mode after switching to another buffer. */ - VIsual_reselect = FALSE; + // Don't restart Select mode after switching to another buffer. + VIsual_reselect = false; // close_windows() or apply_autocmds() may change curbuf and wipe out "buf" prevbuf = curbuf; @@ -1470,8 +1514,9 @@ void set_curbuf(buf_T *buf, int action) ) || curwin->w_buffer == NULL ) { enter_buffer(buf); - if (old_tw != curbuf->b_p_tw) + if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); + } } if (bufref_valid(&prevbufref) && prevbuf->terminal != NULL) { @@ -1486,75 +1531,83 @@ void set_curbuf(buf_T *buf, int action) */ void enter_buffer(buf_T *buf) { - /* Copy buffer and window local option values. Not for a help buffer. */ + // Copy buffer and window local option values. Not for a help buffer. buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); - if (!buf->b_help) + if (!buf->b_help) { get_winopts(buf); - else - /* Remove all folds in the window. */ + } else { + // Remove all folds in the window. clearFolding(curwin); - foldUpdateAll(curwin); /* update folds (later). */ + } + foldUpdateAll(curwin); // update folds (later). - /* Get the buffer in the current window. */ + // Get the buffer in the current window. curwin->w_buffer = buf; curbuf = buf; - ++curbuf->b_nwindows; + curbuf->b_nwindows++; - if (curwin->w_p_diff) + if (curwin->w_p_diff) { diff_buf_add(curbuf); + } - curwin->w_s = &(buf->b_s); + curwin->w_s = &(curbuf->b_s); - /* Cursor on first line by default. */ + // Cursor on first line by default. curwin->w_cursor.lnum = 1; curwin->w_cursor.col = 0; curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; - curwin->w_topline_was_set = FALSE; + curwin->w_set_curswant = true; + curwin->w_topline_was_set = false; - /* mark cursor position as being invalid */ + // mark cursor position as being invalid curwin->w_valid = 0; - /* Make sure the buffer is loaded. */ - if (curbuf->b_ml.ml_mfp == NULL) { /* need to load the file */ - /* If there is no filetype, allow for detecting one. Esp. useful for - * ":ball" used in an autocommand. If there already is a filetype we - * might prefer to keep it. */ - if (*curbuf->b_p_ft == NUL) - did_filetype = FALSE; + // Make sure the buffer is loaded. + if (curbuf->b_ml.ml_mfp == NULL) { // need to load the file + // If there is no filetype, allow for detecting one. Esp. useful for + // ":ball" used in an autocommand. If there already is a filetype we + // might prefer to keep it. + if (*curbuf->b_p_ft == NUL) { + did_filetype = false; + } - open_buffer(FALSE, NULL, 0); + open_buffer(false, NULL, 0); } else { - if (!msg_silent) - need_fileinfo = TRUE; /* display file info after redraw */ - (void)buf_check_timestamp(curbuf, FALSE); /* check if file changed */ + if (!msg_silent) { + need_fileinfo = true; // display file info after redraw + } + (void)buf_check_timestamp(curbuf, false); // check if file changed curwin->w_topline = 1; curwin->w_topfill = 0; - apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); - apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, false, curbuf); } /* If autocommands did not change the cursor position, restore cursor lnum * and possibly cursor col. */ - if (curwin->w_cursor.lnum == 1 && inindent(0)) + if (curwin->w_cursor.lnum == 1 && inindent(0)) { buflist_getfpos(); + } - check_arg_idx(curwin); /* check for valid arg_idx */ + check_arg_idx(curwin); // check for valid arg_idx maketitle(); - /* when autocmds didn't change it */ - if (curwin->w_topline == 1 && !curwin->w_topline_was_set) - scroll_cursor_halfway(FALSE); /* redisplay at correct position */ + // when autocmds didn't change it + if (curwin->w_topline == 1 && !curwin->w_topline_was_set) { + scroll_cursor_halfway(false); // redisplay at correct position + } - /* Change directories when the 'acd' option is set. */ + // Change directories when the 'acd' option is set. do_autochdir(); - if (curbuf->b_kmap_state & KEYMAP_INIT) + if (curbuf->b_kmap_state & KEYMAP_INIT) { (void)keymap_init(); - /* May need to set the spell language. Can only do this after the buffer - * has been properly setup. */ - if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) + } + // May need to set the spell language. Can only do this after the buffer + // has been properly setup. + if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { (void)did_set_spelllang(curwin); + } redraw_later(NOT_VALID); } @@ -1567,6 +1620,7 @@ void do_autochdir(void) if (starting == 0 && curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname) == OK) { + post_chdir(kCdScopeGlobal, false); shorten_fnames(true); } } @@ -1601,11 +1655,11 @@ static inline void buf_init_changedtick(buf_T *const buf) /// Add a file name to the buffer list. /// If the same file name already exists return a pointer to that buffer. /// If it does not exist, or if fname == NULL, a new entry is created. -/// If (flags & BLN_CURBUF) is TRUE, may use current buffer. -/// If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. -/// If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. -/// If (flags & BLN_NEW) is TRUE, don't use an existing buffer. -/// If (flags & BLN_NOOPT) is TRUE, don't copy options from the current buffer +/// If (flags & BLN_CURBUF) is true, may use current buffer. +/// If (flags & BLN_LISTED) is true, add new buffer to buffer list. +/// If (flags & BLN_DUMMY) is true, don't count it as a real buffer. +/// If (flags & BLN_NEW) is true, don't use an existing buffer. +/// If (flags & BLN_NOOPT) is true, don't copy options from the current buffer /// if the buffer already exists. /// This is the ONLY way to create a new buffer. /// @@ -1664,22 +1718,22 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) * buffer.) */ buf = NULL; - if ((flags & BLN_CURBUF) - && curbuf != NULL - && curbuf->b_ffname == NULL - && curbuf->b_nwindows <= 1 - && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY())) { + if ((flags & BLN_CURBUF) && curbuf_reusable()) { + assert(curbuf != NULL); buf = curbuf; /* It's like this buffer is deleted. Watch out for autocommands that * change curbuf! If that happens, allocate a new buffer anyway. */ - if (curbuf->b_p_bl) - apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); - if (buf == curbuf) - apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, FALSE, curbuf); - if (aborting()) /* autocmds may abort script processing */ + if (curbuf->b_p_bl) { + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, false, curbuf); + } + if (buf == curbuf) { + apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, false, curbuf); + } + if (aborting()) { // autocmds may abort script processing return NULL; + } if (buf == curbuf) { - /* Make sure 'bufhidden' and 'buftype' are empty */ + // Make sure 'bufhidden' and 'buftype' are empty clear_string_option(&buf->b_p_bh); clear_string_option(&buf->b_p_bt); } @@ -1688,6 +1742,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) buf = xcalloc(1, sizeof(buf_T)); // init b: variables buf->b_vars = tv_dict_alloc(); + buf->b_signcols_max = -1; init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); buf_init_changedtick(buf); } @@ -1705,35 +1760,38 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) buf->b_ffname = NULL; xfree(buf->b_sfname); buf->b_sfname = NULL; - if (buf != curbuf) + if (buf != curbuf) { free_buffer(buf); + } return NULL; } if (buf == curbuf) { - /* free all things allocated for this buffer */ + // free all things allocated for this buffer buf_freeall(buf, 0); - if (buf != curbuf) /* autocommands deleted the buffer! */ + if (buf != curbuf) { // autocommands deleted the buffer! return NULL; - if (aborting()) /* autocmds may abort script processing */ + } + if (aborting()) { // autocmds may abort script processing return NULL; - free_buffer_stuff(buf, FALSE); /* delete local variables et al. */ + } + free_buffer_stuff(buf, false); // delete local variables et al. - /* Init the options. */ + // Init the options. buf->b_p_initialized = false; buf_copy_options(buf, BCO_ENTER); - /* need to reload lmaps and set b:keymap_name */ + // need to reload lmaps and set b:keymap_name curbuf->b_kmap_state |= KEYMAP_INIT; } else { /* * put new buffer at the end of the buffer list */ buf->b_next = NULL; - if (firstbuf == NULL) { /* buffer list is empty */ + if (firstbuf == NULL) { // buffer list is empty buf->b_prev = NULL; firstbuf = buf; - } else { /* append new buffer at end of list */ + } else { // append new buffer at end of list lastbuf->b_next = buf; buf->b_prev = lastbuf; } @@ -1771,8 +1829,9 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) } buf->b_u_synced = true; buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; - if (flags & BLN_DUMMY) + if (flags & BLN_DUMMY) { buf->b_flags |= BF_DUMMY; + } buf_clear_file(buf); clrallmarks(buf); // clear marks fmarks_check_names(buf); // check file marks for this file @@ -1803,9 +1862,21 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) return buf; } +/// Return true if the current buffer is empty, unnamed, unmodified and used in +/// only one window. That means it can be reused. +bool curbuf_reusable(void) +{ + return (curbuf != NULL + && curbuf->b_ffname == NULL + && curbuf->b_nwindows <= 1 + && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY()) + && !bt_quickfix(curbuf) + && !curbufIsChanged()); +} + /* * Free the memory for the options of a buffer. - * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and + * If "free_p_ff" is true also free 'fileformat', 'buftype' and * 'fileencoding'. */ void free_buf_options(buf_T *buf, int free_p_ff) @@ -1884,25 +1955,28 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) buf = buflist_findnr(n); if (buf == NULL) { - if ((options & GETF_ALT) && n == 0) + if ((options & GETF_ALT) && n == 0) { EMSG(_(e_noalt)); - else + } else { EMSGN(_("E92: Buffer %" PRId64 " not found"), n); + } return FAIL; } - /* if alternate file is the current buffer, nothing to do */ - if (buf == curbuf) + // if alternate file is the current buffer, nothing to do + if (buf == curbuf) { return OK; + } if (text_locked()) { text_locked_msg(); return FAIL; } - if (curbuf_locked()) + if (curbuf_locked()) { return FAIL; + } - /* altfpos may be changed by getfile(), get it now */ + // altfpos may be changed by getfile(), get it now if (lnum == 0) { fpos = buflist_findfpos(buf); lnum = fpos->lnum; @@ -1942,12 +2016,12 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) (options & GETF_SETMARK), lnum, forceit))) { RedrawingDisabled--; - /* cursor is at to BOL and w_cursor.lnum is checked due to getfile() */ + // cursor is at to BOL and w_cursor.lnum is checked due to getfile() if (!p_sol && col != 0) { curwin->w_cursor.col = col; check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } return OK; } @@ -1965,13 +2039,13 @@ void buflist_getfpos(void) curwin->w_cursor.lnum = fpos->lnum; check_cursor_lnum(); - if (p_sol) + if (p_sol) { curwin->w_cursor.col = 0; - else { + } else { curwin->w_cursor.col = fpos->col; check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } } @@ -1984,12 +2058,13 @@ buf_T *buflist_findname_exp(char_u *fname) char_u *ffname; buf_T *buf = NULL; - /* First make the name into a full path name */ + // First make the name into a full path name ffname = (char_u *)FullName_save((char *)fname, #ifdef UNIX - TRUE /* force expansion, get rid of symbolic links */ + // force expansion, get rid of symbolic links + true #else - FALSE + false #endif ); if (ffname != NULL) { @@ -2059,34 +2134,37 @@ int buflist_findpat( if (diffmode && !(found_buf && diff_mode_buf(found_buf))) { match = -1; } - } - /* - * Try four ways of matching a listed buffer: - * attempt == 0: without '^' or '$' (at any position) - * attempt == 1: with '^' at start (only at position 0) - * attempt == 2: with '$' at end (only match at end) - * attempt == 3: with '^' at start and '$' at end (only full match) - * Repeat this for finding an unlisted buffer if there was no matching - * listed buffer. - */ - else { - pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE); - if (pat == NULL) + } else { + // + // Try four ways of matching a listed buffer: + // attempt == 0: without '^' or '$' (at any position) + // attempt == 1: with '^' at start (only at position 0) + // attempt == 2: with '$' at end (only match at end) + // attempt == 3: with '^' at start and '$' at end (only full match) + // Repeat this for finding an unlisted buffer if there was no matching + // listed buffer. + // + + pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, false); + if (pat == NULL) { return -1; + } patend = pat + STRLEN(pat) - 1; toggledollar = (patend > pat && *patend == '$'); - /* First try finding a listed buffer. If not found and "unlisted" - * is TRUE, try finding an unlisted buffer. */ - find_listed = TRUE; + // First try finding a listed buffer. If not found and "unlisted" + // is true, try finding an unlisted buffer. + find_listed = true; for (;; ) { - for (attempt = 0; attempt <= 3; ++attempt) { - /* may add '^' and '$' */ - if (toggledollar) - *patend = (attempt < 2) ? NUL : '$'; /* add/remove '$' */ + for (attempt = 0; attempt <= 3; attempt++) { + // may add '^' and '$' + if (toggledollar) { + *patend = (attempt < 2) ? NUL : '$'; // add/remove '$' + } p = pat; - if (*p == '^' && !(attempt & 1)) /* add/remove '^' */ - ++p; + if (*p == '^' && !(attempt & 1)) { // add/remove '^' + p++; + } regmatch_T regmatch; regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); @@ -2113,33 +2191,36 @@ int buflist_findpat( continue; } } - if (match >= 0) { /* already found a match */ + if (match >= 0) { // already found a match match = -2; break; } - match = buf->b_fnum; /* remember first match */ + match = buf->b_fnum; // remember first match } } vim_regfree(regmatch.regprog); - if (match >= 0) /* found one match */ + if (match >= 0) { // found one match break; + } } /* Only search for unlisted buffers if there was no match with * a listed buffer. */ - if (!unlisted || !find_listed || match != -1) + if (!unlisted || !find_listed || match != -1) { break; - find_listed = FALSE; + } + find_listed = false; } xfree(pat); } - if (match == -2) + if (match == -2) { EMSG2(_("E93: More than one match for %s"), pattern); - else if (match < 0) + } else if (match < 0) { EMSG2(_("E94: No matching buffer for %s"), pattern); + } return match; } @@ -2157,10 +2238,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) int attempt; char_u *patc; - *num_file = 0; /* return values in case of FAIL */ + *num_file = 0; // return values in case of FAIL *file = NULL; - /* Make a copy of "pat" and change "^" to "\(^\|[\/]\)". */ + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)". if (*pat == '^') { patc = xmalloc(STRLEN(pat) + 11); STRCPY(patc, "\\(^\\|[\\/]\\)"); @@ -2172,15 +2253,17 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) * attempt == 0: try match with '\<', match at start of word * attempt == 1: try match without '\<', match anywhere */ - for (attempt = 0; attempt <= 1; ++attempt) { - if (attempt > 0 && patc == pat) - break; /* there was no anchor, no need to try again */ + for (attempt = 0; attempt <= 1; attempt++) { + if (attempt > 0 && patc == pat) { + break; // there was no anchor, no need to try again + } regmatch_T regmatch; regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); if (regmatch.regprog == NULL) { - if (patc != pat) + if (patc != pat) { xfree(patc); + } return FAIL; } @@ -2188,37 +2271,42 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) * round == 1: Count the matches. * round == 2: Build the array to keep the matches. */ - for (round = 1; round <= 2; ++round) { + for (round = 1; round <= 2; round++) { count = 0; FOR_ALL_BUFFERS(buf) { - if (!buf->b_p_bl) /* skip unlisted buffers */ + if (!buf->b_p_bl) { // skip unlisted buffers continue; + } p = buflist_match(®match, buf, p_wic); if (p != NULL) { - if (round == 1) - ++count; - else { - if (options & WILD_HOME_REPLACE) + if (round == 1) { + count++; + } else { + if (options & WILD_HOME_REPLACE) { p = home_replace_save(buf, p); - else + } else { p = vim_strsave(p); + } (*file)[count++] = p; } } } - if (count == 0) /* no match found, break here */ + if (count == 0) { // no match found, break here break; + } if (round == 1) { *file = xmalloc((size_t)count * sizeof(**file)); } } vim_regfree(regmatch.regprog); - if (count) /* match(es) found, break here */ + if (count) { // match(es) found, break here break; + } } - if (patc != pat) + if (patc != pat) { xfree(patc); + } *num_file = count; return count == 0 ? FAIL : OK; @@ -2227,7 +2315,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) /// Check for a match on the file name for buffer "buf" with regprog "prog". /// -/// @param ignore_case When TRUE, ignore case. Use 'fic' otherwise. +/// @param ignore_case When true, ignore case. Use 'fic' otherwise. static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) { // First try the short file name, then the long file name. @@ -2240,7 +2328,7 @@ static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) /// Try matching the regexp in "prog" with file name "name". /// -/// @param ignore_case When TRUE, ignore case. Use 'fileignorecase' otherwise. +/// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. /// @return "name" when there is a match, NULL when not. static char_u *fname_match(regmatch_T *rmp, char_u *name, bool ignore_case) { @@ -2250,13 +2338,14 @@ static char_u *fname_match(regmatch_T *rmp, char_u *name, bool ignore_case) if (name != NULL) { // Ignore case when 'fileignorecase' or the argument is set. rmp->rm_ic = p_fic || ignore_case; - if (vim_regexec(rmp, name, (colnr_T)0)) + if (vim_regexec(rmp, name, (colnr_T)0)) { match = name; - else { - /* Replace $(HOME) with '~' and try matching again. */ + } else { + // Replace $(HOME) with '~' and try matching again. p = home_replace_save(NULL, name); - if (vim_regexec(rmp, p, (colnr_T)0)) + if (vim_regexec(rmp, p, (colnr_T)0)) { match = name; + } xfree(p); } } @@ -2281,17 +2370,18 @@ buf_T *buflist_findnr(int nr) * Returns a pointer to allocated memory, of NULL when failed. */ char_u * -buflist_nr2name ( +buflist_nr2name( int n, int fullname, - int helptail /* for help buffers return tail only */ + int helptail // for help buffers return tail only ) { buf_T *buf; buf = buflist_findnr(n); - if (buf == NULL) + if (buf == NULL) { return NULL; + } return home_replace_save(helptail ? buf : NULL, fullname ? buf->b_ffname : buf->b_fname); } @@ -2311,23 +2401,28 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, { wininfo_T *wip; - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) - if (wip->wi_win == win) + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (wip->wi_win == win) { break; + } + } if (wip == NULL) { - /* allocate a new entry */ + // allocate a new entry wip = xcalloc(1, sizeof(wininfo_T)); wip->wi_win = win; - if (lnum == 0) /* set lnum even when it's 0 */ + if (lnum == 0) { // set lnum even when it's 0 lnum = 1; + } } else { - /* remove the entry from the list */ - if (wip->wi_prev) + // remove the entry from the list + if (wip->wi_prev) { wip->wi_prev->wi_next = wip->wi_next; - else + } else { buf->b_wininfo = wip->wi_next; - if (wip->wi_next) + } + if (wip->wi_next) { wip->wi_next->wi_prev = wip->wi_prev; + } if (copy_options && wip->wi_optset) { clear_winopt(&wip->wi_opt); deleteFoldRecurse(&wip->wi_folds); @@ -2338,19 +2433,20 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, wip->wi_fpos.col = col; } if (copy_options) { - /* Save the window-specific option values. */ + // Save the window-specific option values. copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); wip->wi_fold_manual = win->w_fold_manual; cloneFoldGrowArray(&win->w_folds, &wip->wi_folds); wip->wi_optset = true; } - /* insert the entry in front of the list */ + // insert the entry in front of the list wip->wi_next = buf->b_wininfo; buf->b_wininfo = wip; wip->wi_prev = NULL; - if (wip->wi_next) + if (wip->wi_next) { wip->wi_next->wi_prev = wip; + } return; } @@ -2377,7 +2473,7 @@ static bool wininfo_other_tab_diff(wininfo_T *wip) /* * Find info for the current window in buffer "buf". * If not found, return the info for the most recently used window. - * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in + * When "skip_diff_buffer" is true avoid windows with 'diff' set that is in * another tab page. * Returns NULL when there isn't any info. */ @@ -2395,11 +2491,14 @@ static wininfo_T *find_wininfo(buf_T *buf, int skip_diff_buffer) * 'diff' set and is in another tab page). */ if (wip == NULL) { if (skip_diff_buffer) { - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) - if (!wininfo_other_tab_diff(wip)) + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (!wininfo_other_tab_diff(wip)) { break; - } else + } + } + } else { wip = buf->b_wininfo; + } } return wip; } @@ -2433,9 +2532,10 @@ void get_winopts(buf_T *buf) } else copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); - /* 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; + } didset_window_options(curwin); } @@ -2448,7 +2548,7 @@ pos_T *buflist_findfpos(buf_T *buf) { static pos_T no_position = { 1, 0, 0 }; - wininfo_T *wip = find_wininfo(buf, FALSE); + wininfo_T *wip = find_wininfo(buf, false); return (wip == NULL) ? &no_position : &(wip->wi_fpos); } @@ -2509,7 +2609,7 @@ void buflist_list(exarg_T *eap) len = IOSIZE - 20; } - /* put "line 999" in column 40 or after the file name */ + // put "line 999" in column 40 or after the file name i = 40 - vim_strsize(IObuff); do { IObuff[len++] = ' '; @@ -2519,7 +2619,7 @@ void buflist_list(exarg_T *eap) buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf)); msg_outtrans(IObuff); - ui_flush(); /* output one line at a time */ + ui_flush(); // output one line at a time os_breakcheck(); } } @@ -2535,8 +2635,9 @@ int buflist_name_nr(int fnum, char_u **fname, linenr_T *lnum) buf_T *buf; buf = buflist_findnr(fnum); - if (buf == NULL || buf->b_fname == NULL) + if (buf == NULL || buf->b_fname == NULL) { return FAIL; + } *fname = buf->b_fname; *lnum = buflist_findlnum(buf); @@ -2550,12 +2651,12 @@ int buflist_name_nr(int fnum, char_u **fname, linenr_T *lnum) * Returns FAIL for failure (file name already in use by other buffer) * OK otherwise. */ -int -setfname ( +int +setfname( buf_T *buf, char_u *ffname, char_u *sfname, - int message /* give message when buffer already exists */ + int message // give message when buffer already exists ) { buf_T *obuf = NULL; @@ -2563,15 +2664,16 @@ setfname ( bool file_id_valid = false; if (ffname == NULL || *ffname == NUL) { - /* Removing the name. */ + // Removing the name. xfree(buf->b_ffname); xfree(buf->b_sfname); buf->b_ffname = NULL; buf->b_sfname = NULL; } else { - fname_expand(buf, &ffname, &sfname); /* will allocate ffname */ - if (ffname == NULL) /* out of memory */ + fname_expand(buf, &ffname, &sfname); // will allocate ffname + if (ffname == NULL) { // out of memory return FAIL; + } /* * if the file name is already used in another buffer: @@ -2583,18 +2685,19 @@ setfname ( obuf = buflist_findname_file_id(ffname, &file_id, file_id_valid); } if (obuf != NULL && obuf != buf) { - if (obuf->b_ml.ml_mfp != NULL) { /* it's loaded, fail */ - if (message) + if (obuf->b_ml.ml_mfp != NULL) { // it's loaded, fail + if (message) { EMSG(_("E95: Buffer with this name already exists")); + } xfree(ffname); return FAIL; } - /* delete from the list */ - close_buffer(NULL, obuf, DOBUF_WIPE, FALSE); + // delete from the list + close_buffer(NULL, obuf, DOBUF_WIPE, false); } sfname = vim_strsave(sfname); #ifdef USE_FNAME_CASE - path_fix_case(sfname); /* set correct case for short file name */ + path_fix_case(sfname); // set correct case for short file name #endif xfree(buf->b_ffname); xfree(buf->b_sfname); @@ -2643,15 +2746,17 @@ void buf_name_changed(buf_T *buf) /* * If the file name changed, also change the name of the swapfile */ - if (buf->b_ml.ml_mfp != NULL) + if (buf->b_ml.ml_mfp != NULL) { ml_setname(buf); + } - if (curwin->w_buffer == buf) - check_arg_idx(curwin); /* check file name for arg list */ - maketitle(); /* set window title */ - status_redraw_all(); /* status lines need to be redrawn */ - fmarks_check_names(buf); /* check named file marks */ - ml_timestamp(buf); /* reset timestamp */ + if (curwin->w_buffer == buf) { + check_arg_idx(curwin); // check file name for arg list + } + maketitle(); // set window title + status_redraw_all(); // status lines need to be redrawn + fmarks_check_names(buf); // check named file marks + ml_timestamp(buf); // reset timestamp } /* @@ -2684,8 +2789,9 @@ char_u * getaltfname( linenr_T dummy; if (buflist_name_nr(0, &fname, &dummy) == FAIL) { - if (errmsg) + if (errmsg) { EMSG(_(e_noalt)); + } return NULL; } return fname; @@ -2715,10 +2821,12 @@ int buflist_add(char_u *fname, int flags) void buflist_slash_adjust(void) { FOR_ALL_BUFFERS(bp) { - if (bp->b_ffname != NULL) + if (bp->b_ffname != NULL) { slash_adjust(bp->b_ffname); - if (bp->b_sfname != NULL) + } + if (bp->b_sfname != NULL) { slash_adjust(bp->b_sfname); + } } } @@ -2730,7 +2838,7 @@ void buflist_slash_adjust(void) */ void buflist_altfpos(win_T *win) { - buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, TRUE); + buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, true); } /// Check that "ffname" is not the same file as current file. @@ -2818,9 +2926,9 @@ static bool buf_same_file_id(buf_T *buf, FileID *file_id) /* * Print info about the current buffer. */ -void -fileinfo ( - int fullname, /* when non-zero print full path */ +void +fileinfo( + int fullname, // when non-zero print full path int shorthelp, int dont_truncate ) @@ -2833,20 +2941,21 @@ fileinfo ( buffer = xmalloc(IOSIZE); - if (fullname > 1) { /* 2 CTRL-G: include buffer number */ + if (fullname > 1) { // 2 CTRL-G: include buffer number vim_snprintf((char *)buffer, IOSIZE, "buf %d: ", curbuf->b_fnum); p = buffer + STRLEN(buffer); } else p = buffer; *p++ = '"'; - if (buf_spname(curbuf) != NULL) + if (buf_spname(curbuf) != NULL) { STRLCPY(p, buf_spname(curbuf), IOSIZE - (p - buffer)); - else { - if (!fullname && curbuf->b_fname != NULL) + } else { + if (!fullname && curbuf->b_fname != NULL) { name = curbuf->b_fname; - else + } else { name = curbuf->b_ffname; + } home_replace(shorthelp ? curbuf : NULL, name, p, (size_t)(IOSIZE - (p - buffer)), true); } @@ -2877,12 +2986,13 @@ fileinfo ( if (curbuf->b_ml.ml_flags & ML_EMPTY) { vim_snprintf_add((char *)buffer, IOSIZE, "%s", _(no_lines_msg)); } else if (p_ru) { - /* Current line and column are already on the screen -- webb */ - if (curbuf->b_ml.ml_line_count == 1) + // Current line and column are already on the screen -- webb + if (curbuf->b_ml.ml_line_count == 1) { vim_snprintf_add((char *)buffer, IOSIZE, _("1 line --%d%%--"), n); - else + } else { vim_snprintf_add((char *)buffer, IOSIZE, _("%" PRId64 " lines --%d%%--"), - (int64_t)curbuf->b_ml.ml_line_count, n); + (int64_t)curbuf->b_ml.ml_line_count, n); + } } else { vim_snprintf_add((char *)buffer, IOSIZE, _("line %" PRId64 " of %" PRId64 " --%d%%-- col "), @@ -2902,18 +3012,19 @@ fileinfo ( * First call msg_start() to get the message in the right place. */ msg_start(); n = msg_scroll; - msg_scroll = TRUE; + msg_scroll = true; msg(buffer); msg_scroll = n; } else { - p = msg_trunc_attr(buffer, FALSE, 0); - if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) - /* Need to repeat the message after redrawing when: - * - When restart_edit is set (otherwise there will be a delay - * before redrawing). - * - When the screen was scrolled but there is no wait-return - * prompt. */ + p = msg_trunc_attr(buffer, false, 0); + if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) { + // Need to repeat the message after redrawing when: + // - When restart_edit is set (otherwise there will be a delay + // before redrawing). + // - When the screen was scrolled but there is no wait-return + // prompt. set_keep_msg(p, 0); + } } xfree(buffer); @@ -2921,10 +3032,11 @@ fileinfo ( void col_print(char_u *buf, size_t buflen, int col, int vcol) { - if (col == vcol) + if (col == vcol) { vim_snprintf((char *)buf, buflen, "%d", col); - else + } else { vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol); + } } /* @@ -2945,14 +3057,15 @@ void maketitle(void) char buf[IOSIZE]; if (!redrawing()) { - /* Postpone updating the title when 'lazyredraw' is set. */ - need_maketitle = TRUE; + // Postpone updating the title when 'lazyredraw' is set. + need_maketitle = true; return; } - need_maketitle = FALSE; - if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) + need_maketitle = false; + if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) { return; + } if (p_title) { if (p_titlelen > 0) { @@ -2964,7 +3077,7 @@ void maketitle(void) if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { - int use_sandbox = FALSE; + int use_sandbox = false; int save_called_emsg = called_emsg; use_sandbox = was_set_insecurely((char_u *)"titlestring", 0); @@ -3075,11 +3188,11 @@ void maketitle(void) i_str = (char_u *)buf; if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { - int use_sandbox = FALSE; + int use_sandbox = false; int save_called_emsg = called_emsg; use_sandbox = was_set_insecurely((char_u *)"iconstring", 0); - called_emsg = FALSE; + called_emsg = false; build_stl_str_hl(curwin, i_str, sizeof(buf), p_iconstring, use_sandbox, 0, 0, NULL, NULL); @@ -3090,17 +3203,19 @@ void maketitle(void) } else i_str = p_iconstring; } else { - if (buf_spname(curbuf) != NULL) + if (buf_spname(curbuf) != NULL) { i_name = buf_spname(curbuf); - else /* use file name only in icon */ + } else { // use file name only in icon i_name = path_tail(curbuf->b_ffname); + } *i_str = NUL; - /* Truncate name at 100 bytes. */ + // Truncate name at 100 bytes. len = (int)STRLEN(i_name); if (len > 100) { len -= 100; - if (has_mbyte) + if (has_mbyte) { len += (*mb_tail_off)(i_name, i_name + len) + 1; + } i_name += len; } STRCPY(i_str, i_name); @@ -3110,8 +3225,9 @@ void maketitle(void) mustset |= ti_change(i_str, &lasticon); - if (mustset) + if (mustset) { resettitle(); + } } /// Used for title and icon: Check if "str" differs from "*last". Set "*last" @@ -3233,15 +3349,17 @@ int build_stl_str_hl( // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); - if (usefmt == NULL) + if (usefmt == NULL) { usefmt = fmt; + } } - if (fillchar == 0) + if (fillchar == 0) { fillchar = ' '; - // Can't handle a multi-byte fill character yet. - else if (mb_char2len(fillchar) > 1) + } else if (mb_char2len(fillchar) > 1) { + // Can't handle a multi-byte fill character yet. fillchar = '-'; + } // Get line & check if empty (cursorpos will show "0-1"). char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false); @@ -3296,8 +3414,9 @@ int build_stl_str_hl( // If we have processed the entire format string or run out of // room in our output buffer, exit the loop. - if (*fmt_p == NUL || out_p >= out_end_p) + if (*fmt_p == NUL || out_p >= out_end_p) { break; + } // The rest of this loop will handle a single `%` item. // Note: We increment here to skip over the `%` character we are currently @@ -3381,7 +3500,7 @@ int build_stl_str_hl( // { Determine the number of bytes to remove long n; if (has_mbyte) { - /* Find the first character that should be included. */ + // Find the first character that should be included. n = 0; while (group_len >= items[groupitems[groupdepth]].maxwid) { group_len -= ptr2cells(t + n); @@ -3468,8 +3587,9 @@ int build_stl_str_hl( // The first digit group is the item's min width if (ascii_isdigit(*fmt_p)) { minwid = getdigits_int(&fmt_p); - if (minwid < 0) /* overflow */ + if (minwid < 0) { // overflow minwid = 0; + } } // User highlight groups override the min width field @@ -3553,8 +3673,9 @@ int build_stl_str_hl( fmt_p++; if (ascii_isdigit(*fmt_p)) { maxwid = getdigits_int(&fmt_p); - if (maxwid <= 0) /* overflow */ + if (maxwid <= 0) { // overflow maxwid = 50; + } } } @@ -3606,13 +3727,14 @@ int build_stl_str_hl( home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true); } trans_characters(NameBuff, MAXPATHL); - if (opt != STL_FILENAME) + if (opt != STL_FILENAME) { str = NameBuff; - else + } else { str = path_tail(NameBuff); + } break; } - case STL_VIM_EXPR: /* '{' */ + case STL_VIM_EXPR: // '{' { itemisflag = true; @@ -3621,8 +3743,9 @@ int build_stl_str_hl( char_u *t = out_p; while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p) *out_p++ = *fmt_p++; - if (*fmt_p != '}') /* missing '}' or out of space */ + if (*fmt_p != '}') { // missing '}' or out of space break; + } fmt_p++; *out_p = 0; @@ -3689,7 +3812,7 @@ int build_stl_str_hl( getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); wp->w_p_list = true; } - ++virtcol; + virtcol++; // Don't display %V if it's the same as %c. if (opt == STL_VIRTCOL_ALT && (virtcol == (colnr_T)(!(State & INSERT) && empty_line @@ -3730,8 +3853,9 @@ int build_stl_str_hl( case STL_KEYMAP: fillable = false; - if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN)) + if (get_keymap_str(wp, (char_u *)"<%s>", tmp, TMPLEN)) { str = tmp; + } break; case STL_PAGENUM: num = printer_page_num; @@ -3758,17 +3882,19 @@ int build_stl_str_hl( FALLTHROUGH; case STL_BYTEVAL: num = byteval; - if (num == NL) + if (num == NL) { num = 0; - else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) + } else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) { num = NL; + } break; case STL_ROFLAG: case STL_ROFLAG_ALT: itemisflag = true; - if (wp->w_buffer->b_p_ro) + if (wp->w_buffer->b_p_ro) { str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]")); + } break; case STL_HELPFLAG: @@ -3844,7 +3970,7 @@ int build_stl_str_hl( // { The name of the highlight is surrounded by `#` char_u *t = fmt_p; while (*fmt_p != '#' && *fmt_p != NUL) { - ++fmt_p; + fmt_p++; } // } @@ -3899,8 +4025,9 @@ int build_stl_str_hl( } // Early out if there isn't enough room for the truncation marker - if (out_p >= out_end_p) + if (out_p >= out_end_p) { break; + } // Add the truncation marker *out_p++ = '<'; @@ -3911,10 +4038,11 @@ int build_stl_str_hl( if (minwid > 0) { for (; l < minwid && out_p < out_end_p; l++) { // Don't put a "-" in front of a digit. - if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) + if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { *out_p++ = ' '; - else + } else { *out_p++ = fillchar; + } } minwid = 0; } else { @@ -3941,8 +4069,9 @@ int build_stl_str_hl( // Otherwise if the item is a number, copy that to the output buffer. } else if (num >= 0) { - if (out_p + 20 > out_end_p) - break; /* not sufficient space */ + if (out_p + 20 > out_end_p) { + break; // not sufficient space + } prevchar_isitem = true; // { Build the formatting string @@ -4028,8 +4157,9 @@ int build_stl_str_hl( xfree(str); } - if (num >= 0 || (!itemisflag && str && *str)) - prevchar_isflag = false; /* Item not NULL, but not a flag */ + if (num >= 0 || (!itemisflag && str && *str)) { + prevchar_isflag = false; // Item not NULL, but not a flag + } // Item processed, move to the next curitem++; @@ -4082,8 +4212,9 @@ int build_stl_str_hl( width = 0; for (;; ) { width += ptr2cells(trunc_p); - if (width >= maxwidth) + if (width >= maxwidth) { break; + } // Note: Only advance the pointer if the next // character will fit in the available output space @@ -4287,8 +4418,8 @@ void get_rel_pos(win_T *wp, char_u *buf, int buflen) return; } - long above; /* number of lines above window */ - long below; /* number of lines below window */ + long above; // number of lines above window + long below; // number of lines below window above = wp->w_topline - 1; above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill; @@ -4298,14 +4429,15 @@ void get_rel_pos(win_T *wp, char_u *buf, int buflen) above = 0; } below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; - if (below <= 0) + if (below <= 0) { STRLCPY(buf, (above == 0 ? _("All") : _("Bot")), buflen); - else if (above <= 0) + } else if (above <= 0) { STRLCPY(buf, _("Top"), buflen); - else + } else { vim_snprintf((char *)buf, (size_t)buflen, "%2d%%", above > 1000000L - ? (int)(above / ((above + below) / 100L)) - : (int)(above * 100L / (above + below))); + ? (int)(above / ((above + below) / 100L)) + : (int)(above * 100L / (above + below))); + } } /// Append (file 2 of 8) to "buf[buflen]", if editing more than one file. @@ -4350,11 +4482,13 @@ static bool append_arg_number(win_T *wp, char_u *buf, int buflen, bool add_file) */ void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) { - if (*ffname == NULL) /* if no file name given, nothing to do */ + if (*ffname == NULL) { // if no file name given, nothing to do return; - if (*sfname == NULL) /* if no short file name given, use ffname */ + } + if (*sfname == NULL) { // if no short file name given, use ffname *sfname = *ffname; - *ffname = (char_u *)fix_fname((char *)*ffname); /* expand to full path */ + } + *ffname = (char_u *)fix_fname((char *)*ffname); // expand to full path #ifdef WIN32 if (!buf->b_p_bin) { @@ -4376,35 +4510,36 @@ char_u *alist_name(aentry_T *aep) { buf_T *bp; - /* Use the name from the associated buffer if it exists. */ + // Use the name from the associated buffer if it exists. bp = buflist_findnr(aep->ae_fnum); - if (bp == NULL || bp->b_fname == NULL) + if (bp == NULL || bp->b_fname == NULL) { return aep->ae_fname; + } return bp->b_fname; } /* * do_arg_all(): Open up to 'count' windows, one for each argument. */ -void -do_arg_all ( +void +do_arg_all( int count, - int forceit, /* hide buffers in current windows */ - int keep_tabs /* keep current tabs, for ":tab drop file" */ + int forceit, // hide buffers in current windows + int keep_tabs // keep current tabs, for ":tab drop file" ) { int i; - char_u *opened; /* Array of weight for which args are open: - * 0: not opened - * 1: opened in other tab - * 2: opened in curtab - * 3: opened in curtab and curwin - */ - int opened_len; /* length of opened[] */ - int use_firstwin = FALSE; /* use first window for arglist */ + char_u *opened; // Array of weight for which args are open: + // 0: not opened + // 1: opened in other tab + // 2: opened in curtab + // 3: opened in curtab and curwin + + int opened_len; // length of opened[] + int use_firstwin = false; // use first window for arglist int split_ret = OK; bool p_ea_save; - alist_T *alist; /* argument list to be used */ + alist_T *alist; // argument list to be used buf_T *buf; tabpage_T *tpnext; int had_tab = cmdmod.tab; @@ -4429,7 +4564,7 @@ do_arg_all ( * freed while we are working here by "locking" it. We still have to * watch out for its size to be changed. */ alist = curwin->w_alist; - ++alist->al_refcount; + alist->al_refcount++; old_curwin = curwin; old_curtab = curtab; @@ -4442,8 +4577,9 @@ do_arg_all ( * Windows that have a changed buffer and can't be hidden won't be closed. * When the ":tab" modifier was used do this for all tab pages. */ - if (had_tab > 0) - goto_tabpage_tp(first_tabpage, TRUE, TRUE); + if (had_tab > 0) { + goto_tabpage_tp(first_tabpage, true, true); + } for (;; ) { win_T *wpnext = NULL; tpnext = curtab->tp_next; @@ -4455,7 +4591,7 @@ do_arg_all ( i = opened_len; } else { // check if the buffer in this window is in the arglist - for (i = 0; i < opened_len; ++i) { + for (i = 0; i < opened_len; i++) { if (i < alist->al_ga.ga_len && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum || path_full_compare(alist_name(&AARGLIST(alist)[i]), @@ -4463,28 +4599,31 @@ do_arg_all ( int weight = 1; if (old_curtab == curtab) { - ++weight; - if (old_curwin == wp) - ++weight; + weight++; + if (old_curwin == wp) { + weight++; + } } if (weight > (int)opened[i]) { opened[i] = (char_u)weight; if (i == 0) { - if (new_curwin != NULL) + if (new_curwin != NULL) { new_curwin->w_arg_idx = opened_len; + } new_curwin = wp; new_curtab = curtab; } - } else if (keep_tabs) + } else if (keep_tabs) { i = opened_len; + } if (wp->w_alist != alist) { /* Use the current argument list for all windows * containing a file from it. */ alist_unlink(wp->w_alist); wp->w_alist = alist; - ++wp->w_alist->al_refcount; + wp->w_alist->al_refcount++; } break; } @@ -4516,33 +4655,36 @@ do_arg_all ( // check if autocommands removed the next window if (!win_valid(wpnext)) { // start all over... - wpnext = firstwin; + wpnext = firstwin; } } } } } - /* Without the ":tab" modifier only do the current tab page. */ - if (had_tab == 0 || tpnext == NULL) + // Without the ":tab" modifier only do the current tab page. + if (had_tab == 0 || tpnext == NULL) { break; + } - /* check if autocommands removed the next tab page */ - if (!valid_tabpage(tpnext)) - tpnext = first_tabpage; /* start all over...*/ - goto_tabpage_tp(tpnext, TRUE, TRUE); + // check if autocommands removed the next tab page + if (!valid_tabpage(tpnext)) { + tpnext = first_tabpage; // start all over... + } + goto_tabpage_tp(tpnext, true, true); } /* * Open a window for files in the argument list that don't have one. * ARGCOUNT may change while doing this, because of autocommands. */ - if (count > opened_len || count <= 0) + if (count > opened_len || count <= 0) { count = opened_len; + } - /* Don't execute Win/Buf Enter/Leave autocommands here. */ - ++autocmd_no_enter; - ++autocmd_no_leave; + // Don't execute Win/Buf Enter/Leave autocommands here. + autocmd_no_enter++; + autocmd_no_leave++; last_curwin = curwin; last_curtab = curtab; win_enter(lastwin, false); @@ -4558,7 +4700,7 @@ do_arg_all ( arg_had_last = true; } if (opened[i] > 0) { - /* Move the already present window to below the current window */ + // Move the already present window to below the current window if (curwin->w_arg_idx != i) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_arg_idx == i) { @@ -4573,15 +4715,17 @@ do_arg_all ( } } } else if (split_ret == OK) { - if (!use_firstwin) { /* split current window */ + if (!use_firstwin) { // split current window p_ea_save = p_ea; - p_ea = true; /* use space from all windows */ + p_ea = true; // use space from all windows split_ret = win_split(0, WSP_ROOM | WSP_BELOW); p_ea = p_ea_save; - if (split_ret == FAIL) + if (split_ret == FAIL) { continue; - } else /* first window: do autocmd for leaving this buffer */ - --autocmd_no_leave; + } + } else { // first window: do autocmd for leaving this buffer + autocmd_no_leave--; + } /* * edit file "i" @@ -4603,12 +4747,13 @@ do_arg_all ( } os_breakcheck(); - /* When ":tab" was used open a new tab for a new window repeatedly. */ - if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) + // When ":tab" was used open a new tab for a new window repeatedly. + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { cmdmod.tab = 9999; + } } - /* Remove the "lock" on the argument list. */ + // Remove the "lock" on the argument list. alist_unlink(alist); autocmd_no_enter--; @@ -4629,7 +4774,7 @@ do_arg_all ( win_enter(new_curwin, false); } - --autocmd_no_leave; + autocmd_no_leave--; xfree(opened); } @@ -4645,18 +4790,20 @@ void ex_buffer_all(exarg_T *eap) int open_wins = 0; int r; long count; // Maximum number of windows to open. - int all; // When TRUE also load inactive buffers. + int all; // When true also load inactive buffers. int had_tab = cmdmod.tab; tabpage_T *tpnext; - if (eap->addr_count == 0) /* make as many windows as possible */ + if (eap->addr_count == 0) { // make as many windows as possible count = 9999; - else - count = eap->line2; /* make as many windows as specified */ - if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide) - all = FALSE; - else - all = TRUE; + } else { + count = eap->line2; // make as many windows as specified + } + if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide) { + all = false; + } else { + all = true; + } setpcmark(); @@ -4665,8 +4812,9 @@ void ex_buffer_all(exarg_T *eap) * Close superfluous windows (two windows for the same buffer). * Also close windows that are not full-width. */ - if (had_tab > 0) - goto_tabpage_tp(first_tabpage, TRUE, TRUE); + if (had_tab > 0) { + goto_tabpage_tp(first_tabpage, true, true); + } for (;; ) { tpnext = curtab->tp_next; for (wp = firstwin; wp != NULL; wp = wpnext) { @@ -4685,44 +4833,51 @@ void ex_buffer_all(exarg_T *eap) // something strange with windows tpnext = first_tabpage; // start all over... open_wins = 0; - } else - ++open_wins; + } else { + open_wins++; + } } - /* Without the ":tab" modifier only do the current tab page. */ - if (had_tab == 0 || tpnext == NULL) + // Without the ":tab" modifier only do the current tab page. + if (had_tab == 0 || tpnext == NULL) { break; - goto_tabpage_tp(tpnext, TRUE, TRUE); + } + goto_tabpage_tp(tpnext, true, true); } - /* - * Go through the buffer list. When a buffer doesn't have a window yet, - * open one. Otherwise move the window to the right position. - * Watch out for autocommands that delete buffers or windows! - */ - /* Don't execute Win/Buf Enter/Leave autocommands here. */ - ++autocmd_no_enter; + // + // Go through the buffer list. When a buffer doesn't have a window yet, + // open one. Otherwise move the window to the right position. + // Watch out for autocommands that delete buffers or windows! + // + // Don't execute Win/Buf Enter/Leave autocommands here. + autocmd_no_enter++; win_enter(lastwin, false); - ++autocmd_no_leave; + autocmd_no_leave++; for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) { - /* Check if this buffer needs a window */ - if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl) + // Check if this buffer needs a window + if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl) { continue; + } if (had_tab != 0) { - /* With the ":tab" modifier don't move the window. */ - if (buf->b_nwindows > 0) - wp = lastwin; /* buffer has a window, skip it */ - else + // With the ":tab" modifier don't move the window. + if (buf->b_nwindows > 0) { + wp = lastwin; // buffer has a window, skip it + } else { wp = NULL; + } } else { - /* Check if this buffer already has a window */ - for (wp = firstwin; wp != NULL; wp = wp->w_next) - if (wp->w_buffer == buf) + // Check if this buffer already has a window + for (wp = firstwin; wp != NULL; wp = wp->w_next) { + if (wp->w_buffer == buf) { break; - /* If the buffer already has a window, move it */ - if (wp != NULL) + } + } + // If the buffer already has a window, move it + if (wp != NULL) { win_move_after(wp, curwin); + } } if (wp == NULL && split_ret == OK) { @@ -4730,14 +4885,15 @@ void ex_buffer_all(exarg_T *eap) set_bufref(&bufref, buf); // Split the window and put the buffer in it. p_ea_save = p_ea; - p_ea = true; /* use space from all windows */ + p_ea = true; // use space from all windows split_ret = win_split(0, WSP_ROOM | WSP_BELOW); - ++open_wins; + open_wins++; p_ea = p_ea_save; - if (split_ret == FAIL) + if (split_ret == FAIL) { continue; + } - /* Open the buffer in this window. */ + // Open the buffer in this window. swap_exists_action = SEA_DIALOG; set_curbuf(buf, DOBUF_GOTO); if (!bufref_valid(&bufref)) { @@ -4748,15 +4904,15 @@ void ex_buffer_all(exarg_T *eap) if (swap_exists_action == SEA_QUIT) { cleanup_T cs; - /* Reset the error/interrupt/exception state here so that - * aborting() returns FALSE when closing a window. */ + // Reset the error/interrupt/exception state here so that + // aborting() returns false when closing a window. enter_cleanup(&cs); // User selected Quit at ATTENTION prompt; close this window. win_close(curwin, true); open_wins--; swap_exists_action = SEA_NONE; - swap_exists_did_quit = TRUE; + swap_exists_did_quit = true; /* Restore the error/interrupt/exception state if not * discarded by a new aborting error, interrupt, or uncaught @@ -4768,19 +4924,21 @@ void ex_buffer_all(exarg_T *eap) os_breakcheck(); if (got_int) { - (void)vgetc(); /* only break the file loading, not the rest */ + (void)vgetc(); // only break the file loading, not the rest break; } - /* Autocommands deleted the buffer or aborted script processing!!! */ - if (aborting()) + // Autocommands deleted the buffer or aborted script processing!!! + if (aborting()) { break; - /* When ":tab" was used open a new tab for a new window repeatedly. */ - if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) + } + // When ":tab" was used open a new tab for a new window repeatedly. + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { cmdmod.tab = 9999; + } } - --autocmd_no_enter; - win_enter(firstwin, false); /* back to first window */ - --autocmd_no_leave; + autocmd_no_enter--; + win_enter(firstwin, false); // back to first window + autocmd_no_leave--; /* * Close superfluous windows. @@ -4789,7 +4947,7 @@ void ex_buffer_all(exarg_T *eap) r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) || autowrite(wp->w_buffer, false) == OK); if (!win_valid(wp)) { - /* BufWrite Autocommands made the window invalid, start over */ + // BufWrite Autocommands made the window invalid, start over wp = lastwin; } else if (r) { win_close(wp, !buf_hide(wp->w_buffer)); @@ -4797,8 +4955,9 @@ void ex_buffer_all(exarg_T *eap) wp = lastwin; } else { wp = wp->w_prev; - if (wp == NULL) + if (wp == NULL) { break; + } } } } @@ -4820,40 +4979,46 @@ void do_modelines(int flags) int nmlines; static int entered = 0; - if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0) + if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0) { return; + } /* Disallow recursive entry here. Can happen when executing a modeline * triggers an autocommand, which reloads modelines with a ":do". */ - if (entered) + if (entered) { return; + } - ++entered; + entered++; for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines; - ++lnum) - if (chk_modeline(lnum, flags) == FAIL) + lnum++) { + if (chk_modeline(lnum, flags) == FAIL) { nmlines = 0; + } + } for (lnum = curbuf->b_ml.ml_line_count; lnum > 0 && lnum > nmlines - && lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum) - if (chk_modeline(lnum, flags) == FAIL) + && lnum > curbuf->b_ml.ml_line_count - nmlines; lnum--) { + if (chk_modeline(lnum, flags) == FAIL) { nmlines = 0; - --entered; + } + } + entered--; } /* * chk_modeline() - check a single line for a mode string * Return FAIL if an error encountered. */ -static int -chk_modeline ( +static int +chk_modeline( linenr_T lnum, - int flags /* Same as for do_modelines(). */ + int flags // Same as for do_modelines(). ) { char_u *s; char_u *e; - char_u *linecopy; /* local copy of any modeline found */ + char_u *linecopy; // local copy of any modeline found int prev; intmax_t vers; int end; @@ -4863,17 +5028,18 @@ chk_modeline ( scid_T save_SID; prev = -1; - for (s = ml_get(lnum); *s != NUL; ++s) { + for (s = ml_get(lnum); *s != NUL; s++) { if (prev == -1 || ascii_isspace(prev)) { if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0) || STRNCMP(s, "vi:", (size_t)3) == 0) break; - /* Accept both "vim" and "Vim". */ + // Accept both "vim" and "Vim". if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm') { - if (s[3] == '<' || s[3] == '=' || s[3] == '>') + if (s[3] == '<' || s[3] == '=' || s[3] == '>') { e = s + 4; - else + } else { e = s + 3; + } if (getdigits_safe(&e, &vers) != OK) { continue; } @@ -4897,32 +5063,36 @@ chk_modeline ( return retval; } - do /* skip over "ex:", "vi:" or "vim:" */ - ++s; - while (s[-1] != ':'); + do { // skip over "ex:", "vi:" or "vim:" + s++; + } while (s[-1] != ':'); - s = linecopy = vim_strsave(s); /* copy the line, it will change */ + s = linecopy = vim_strsave(s); // copy the line, it will change save_sourcing_lnum = sourcing_lnum; save_sourcing_name = sourcing_name; - sourcing_lnum = lnum; /* prepare for emsg() */ + sourcing_lnum = lnum; // prepare for emsg() sourcing_name = (char_u *)"modelines"; - end = FALSE; - while (end == FALSE) { + end = false; + while (end == false) { s = skipwhite(s); - if (*s == NUL) + if (*s == NUL) { break; + } /* * Find end of set command: ':' or end of line. * Skip over "\:", replacing it with ":". */ - for (e = s; *e != ':' && *e != NUL; ++e) - if (e[0] == '\\' && e[1] == ':') + for (e = s; *e != ':' && *e != NUL; e++) { + if (e[0] == '\\' && e[1] == ':') { STRMOVE(e, e + 1); - if (*e == NUL) - end = TRUE; + } + } + if (*e == NUL) { + end = true; + } /* * If there is a "set" command, require a terminating ':' and @@ -4933,22 +5103,30 @@ chk_modeline ( */ if (STRNCMP(s, "set ", (size_t)4) == 0 || STRNCMP(s, "se ", (size_t)3) == 0) { - if (*e != ':') /* no terminating ':'? */ + if (*e != ':') { // no terminating ':'? break; - end = TRUE; + } + end = true; s = vim_strchr(s, ' ') + 1; } - *e = NUL; /* truncate the set command */ + *e = NUL; // truncate the set command - if (*s != NUL) { /* skip over an empty "::" */ + 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 = 1; + retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags); + + secure = secure_save; current_SID = save_SID; - if (retval == FAIL) /* stop if error found */ + if (retval == FAIL) { // stop if error found break; + } } - s = e + 1; /* advance to next part */ + s = e + 1; // advance to next part } sourcing_lnum = save_sourcing_lnum; @@ -4965,6 +5143,55 @@ bool bt_help(const buf_T *const buf) return buf != NULL && buf->b_help; } +// Return true if "buf" is the quickfix buffer. +bool bt_quickfix(const buf_T *const buf) +{ + return buf != NULL && buf->b_p_bt[0] == 'q'; +} + +// Return true if "buf" is a terminal buffer. +bool bt_terminal(const buf_T *const buf) +{ + return buf != NULL && buf->b_p_bt[0] == 't'; +} + +// Return true if "buf" is a "nofile", "acwrite" or "terminal" buffer. +// This means the buffer name is not a file name. +bool bt_nofile(const buf_T *const buf) +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 'a' || buf->terminal); +} + +// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer. +bool bt_dontwrite(const buf_T *const buf) +{ + return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal); +} + +bool bt_dontwrite_msg(const buf_T *const buf) +{ + if (bt_dontwrite(buf)) { + EMSG(_("E382: Cannot write, 'buftype' option is set")); + return true; + } + return false; +} + +// Return true if the buffer should be hidden, according to 'hidden', ":hide" +// and 'bufhidden'. +bool buf_hide(const buf_T *const buf) +{ + // 'bufhidden' overrules 'hidden' and ":hide", check it first + switch (buf->b_p_bh[0]) { + case 'u': // "unload" + case 'w': // "wipe" + case 'd': return false; // "delete" + case 'h': return true; // "hide" + } + return p_hid || cmdmod.hide; +} + /* * Return special buffer name. * Returns NULL when the buffer has a normal file name. @@ -4979,20 +5206,23 @@ char_u *buf_spname(buf_T *buf) * For location list window, w_llist_ref points to the location list. * For quickfix window, w_llist_ref is NULL. */ - if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) + if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) { return (char_u *)_(msg_loclist); - else + } else { 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) + if (buf->b_sfname != NULL) { return buf->b_sfname; + } return (char_u *)_("[Scratch]"); } - if (buf->b_fname == NULL) + if (buf->b_fname == NULL) { return (char_u *)_("[No Name]"); + } return NULL; } @@ -5024,19 +5254,24 @@ bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) * Insert the sign into the signlist. */ static void insert_sign( - buf_T *buf, /* buffer to store sign in */ - signlist_T *prev, /* previous sign entry */ - signlist_T *next, /* next sign entry */ - int id, /* sign ID */ - linenr_T lnum, /* line number which gets the mark */ - int typenr /* typenr of sign we are adding */ - ) + buf_T *buf, // buffer to store sign in + signlist_T *prev, // previous sign entry + signlist_T *next, // next sign entry + int id, // sign ID + linenr_T lnum, // line number which gets the mark + int typenr // typenr of sign we are adding +) { signlist_T *newsign = xmalloc(sizeof(signlist_T)); newsign->id = id; newsign->lnum = lnum; newsign->typenr = typenr; newsign->next = next; + newsign->prev = prev; + if (next != NULL) { + next->prev = newsign; + } + buf->b_signcols_max = -1; if (prev == NULL) { /* When adding first sign need to redraw the windows to create the @@ -5046,7 +5281,7 @@ static void insert_sign( changed_cline_bef_curs(); } - /* first sign in signlist */ + // first sign in signlist buf->b_signlist = newsign; } else { @@ -5054,18 +5289,109 @@ static void insert_sign( } } +static int sign_compare(const void *a1, const void *a2) +{ + const signlist_T *s1 = *(const signlist_T **)a1; + const signlist_T *s2 = *(const signlist_T **)a2; + + // Sort by line number and the by id + + if (s1->lnum > s2->lnum) { + return 1; + } + if (s1->lnum < s2->lnum) { + return -1; + } + if (s1->id > s2->id) { + return 1; + } + if (s1->id < s2->id) { + return -1; + } + + return 0; +} + +int buf_signcols(buf_T *buf) +{ + if (buf->b_signcols_max == -1) { + signlist_T *sign; // a sign in the signlist + signlist_T **signs_array; + signlist_T **prev_sign; + int nr_signs = 0, i = 0, same; + + // Count the number of signs + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + nr_signs++; + } + + // Make an array of all the signs + signs_array = xcalloc((size_t)nr_signs, sizeof(*sign)); + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + signs_array[i] = sign; + i++; + } + + // Sort the array + qsort(signs_array, (size_t)nr_signs, sizeof(signlist_T *), + sign_compare); + + // Find the maximum amount of signs existing in a single line + buf->b_signcols_max = 0; + + same = 1; + for (i = 1; i < nr_signs; i++) { + if (signs_array[i - 1]->lnum != signs_array[i]->lnum) { + if (buf->b_signcols_max < same) { + buf->b_signcols_max = same; + } + same = 1; + } else { + same++; + } + } + + if (nr_signs > 0 && buf->b_signcols_max < same) { + buf->b_signcols_max = same; + } + + // Recreate the linked list with the sorted order of the array + buf->b_signlist = NULL; + prev_sign = &buf->b_signlist; + + for (i = 0; i < nr_signs; i++) { + sign = signs_array[i]; + sign->next = NULL; + *prev_sign = sign; + + prev_sign = &sign->next; + } + + xfree(signs_array); + + // Check if we need to redraw + if (buf->b_signcols_max != buf->b_signcols) { + buf->b_signcols = buf->b_signcols_max; + redraw_buf_later(buf, NOT_VALID); + } + } + + return buf->b_signcols; +} + /* * Add the sign into the signlist. Find the right spot to do it though. */ void buf_addsign( - buf_T *buf, /* buffer to store sign in */ - int id, /* sign ID */ - linenr_T lnum, /* line number which gets the mark */ - int typenr /* typenr of sign we are adding */ - ) + buf_T *buf, // buffer to store sign in + int id, // sign ID + linenr_T lnum, // line number which gets the mark + int typenr // typenr of sign we are adding +) { - signlist_T *sign; /* a sign in the signlist */ - signlist_T *prev; /* the previous sign */ + signlist_T **lastp; // pointer to pointer to current sign + signlist_T *sign; // a sign in the signlist + signlist_T *prev; // the previous sign prev = NULL; for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { @@ -5073,26 +5399,57 @@ void buf_addsign( sign->typenr = typenr; return; } else if ((lnum == sign->lnum && id != sign->id) - || (id < 0 && lnum < sign->lnum)) { // attempt to keep signs sorted by lnum - insert_sign(buf, prev, sign, id, lnum, typenr); - return; + || (id < 0 && lnum < sign->lnum)) { + // keep signs sorted by lnum: insert new sign at head of list for + // this lnum + while (prev != NULL && prev->lnum == lnum) { + prev = prev->prev; + } + if (prev == NULL) { + sign = buf->b_signlist; + } else { + sign = prev->next; + } + insert_sign(buf, prev, sign, id, lnum, typenr); + return; } prev = sign; } + + // insert new sign at head of list for this lnum + while (prev != NULL && prev->lnum == lnum) { + prev = prev->prev; + } + if (prev == NULL) { + sign = buf->b_signlist; + } else { + sign = prev->next; + } insert_sign(buf, prev, sign, id, lnum, typenr); - return; + // Having more than one sign with _the same type_ and on the _same line_ is + // unwanted, let's prevent it. + + lastp = &buf->b_signlist; + for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { + if (lnum == sign->lnum && sign->typenr == typenr && id != sign->id) { + *lastp = sign->next; + xfree(sign); + } else { + lastp = &sign->next; + } + } } // For an existing, placed sign "markId" change the type to "typenr". // Returns the line number of the sign, or zero if the sign is not found. linenr_T buf_change_sign_type( - buf_T *buf, /* buffer to store sign in */ - int markId, /* sign ID */ - int typenr /* typenr of sign we are adding */ - ) + buf_T *buf, // buffer to store sign in + int markId, // sign ID + int typenr // typenr of sign we are adding +) { - signlist_T *sign; /* a sign in the signlist */ + signlist_T *sign; // a sign in the signlist for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->id == markId) { @@ -5104,16 +5461,23 @@ linenr_T buf_change_sign_type( return (linenr_T)0; } + /// Gets a sign from a given line. -/// In case of multiple signs, returns the most recently placed one. /// /// @param buf Buffer in which to search /// @param lnum Line in which to search /// @param type Type of sign to look for -/// @return Identifier of the first matching sign, or 0 -int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type) +/// @param idx if there multiple signs, this index will pick the n-th +// out of the most `max_signs` sorted ascending by Id. +/// @param max_signs the number of signs, with priority for the ones +// with the highest Ids. +/// @return Identifier of the matching sign, or 0 +int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type, + int idx, int max_signs) { signlist_T *sign; // a sign in a b_signlist + signlist_T *matches[9]; + int nr_matches = 0; for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->lnum == lnum @@ -5124,29 +5488,50 @@ int buf_getsigntype(buf_T *buf, linenr_T lnum, SignType type) && sign_get_attr(sign->typenr, SIGN_LINEHL) != 0) || (type == SIGN_NUMHL && sign_get_attr(sign->typenr, SIGN_NUMHL) != 0))) { - return sign->typenr; + matches[nr_matches] = sign; + nr_matches++; + + if (nr_matches == ARRAY_SIZE(matches)) { + break; + } } } + + if (nr_matches > 0) { + if (nr_matches > max_signs) { + idx += nr_matches - max_signs; + } + + if (idx >= nr_matches) { + return 0; + } + + return matches[idx]->typenr; + } + return 0; } - linenr_T buf_delsign( - buf_T *buf, /* buffer sign is stored in */ - int id /* sign id */ - ) + buf_T *buf, // buffer sign is stored in + int id // sign id +) { - signlist_T **lastp; /* pointer to pointer to current sign */ - signlist_T *sign; /* a sign in a b_signlist */ - signlist_T *next; /* the next sign in a b_signlist */ - linenr_T lnum; /* line number whose sign was deleted */ + signlist_T **lastp; // pointer to pointer to current sign + signlist_T *sign; // a sign in a b_signlist + signlist_T *next; // the next sign in a b_signlist + linenr_T lnum; // line number whose sign was deleted + buf->b_signcols_max = -1; lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { next = sign->next; if (sign->id == id) { *lastp = next; + if (next != NULL) { + next->prev = sign->prev; + } lnum = sign->lnum; xfree(sign); break; @@ -5172,11 +5557,11 @@ linenr_T buf_delsign( * get loaded. */ int buf_findsign( - buf_T *buf, /* buffer to store sign in */ - int id /* sign ID */ - ) + buf_T *buf, // buffer to store sign in + int id // sign ID +) { - signlist_T *sign; /* a sign in the signlist */ + signlist_T *sign; // a sign in the signlist for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->id == id) { @@ -5188,11 +5573,11 @@ int buf_findsign( } int buf_findsign_id( - buf_T *buf, /* buffer whose sign we are searching for */ - linenr_T lnum /* line number of sign */ - ) + buf_T *buf, // buffer whose sign we are searching for + linenr_T lnum // line number of sign +) { - signlist_T *sign; /* a sign in the signlist */ + signlist_T *sign; // a sign in the signlist for (sign = buf->b_signlist; sign != NULL; sign = sign->next) { if (sign->lnum == lnum) { @@ -5223,6 +5608,7 @@ void buf_delete_signs(buf_T *buf) xfree(buf->b_signlist); buf->b_signlist = next; } + buf->b_signcols_max = -1; } /* @@ -5277,18 +5663,27 @@ void sign_list_placed(buf_T *rbuf) */ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after) { - signlist_T *sign; /* a sign in a b_signlist */ + signlist_T *sign; // a sign in a b_signlist + signlist_T *next; // the next sign in a b_signlist + signlist_T **lastp; // pointer to pointer to current sign + + curbuf->b_signcols_max = -1; + lastp = &curbuf->b_signlist; - for (sign = curbuf->b_signlist; sign != NULL; sign = sign->next) { + for (sign = curbuf->b_signlist; sign != NULL; sign = next) { + next = sign->next; if (sign->lnum >= line1 && sign->lnum <= line2) { if (amount == MAXLNUM) { - sign->lnum = line1; + *lastp = next; + xfree(sign); + continue; } else { sign->lnum += amount; } - } - else if (sign->lnum > line2) + } else if (sign->lnum > line2) { sign->lnum += amount_after; + } + lastp = &sign->next; } } @@ -5642,10 +6037,11 @@ void set_buflisted(int on) { if (on != curbuf->b_p_bl) { curbuf->b_p_bl = on; - if (on) - apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf); - else - apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); + if (on) { + apply_autocmds(EVENT_BUFADD, NULL, NULL, false, curbuf); + } else { + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, false, curbuf); + } } } @@ -5681,7 +6077,7 @@ bool buf_contents_changed(buf_T *buf) // compare the two files line by line if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) { differ = false; - for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { if (STRCMP(ml_get_buf(buf, lnum, false), ml_get(lnum)) != 0) { differ = true; break; @@ -5706,10 +6102,10 @@ bool buf_contents_changed(buf_T *buf) * this buffer. Call this to wipe out a temp buffer that does not contain any * marks. */ -void -wipe_buffer ( +void +wipe_buffer( buf_T *buf, - int aucmd /* When TRUE trigger autocommands. */ + int aucmd // When true trigger autocommands. ) { if (!aucmd) { diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 64c906fc96..ee3fda5f6d 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -74,6 +74,8 @@ static inline void buf_set_changedtick(buf_T *const buf, static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T changedtick) { + typval_T old_val = buf->changedtick_di.di_tv; + #ifndef NDEBUG dictitem_T *const changedtick_di = tv_dict_find( buf->b_vars, S_LEN("changedtick")); @@ -87,6 +89,13 @@ static inline void buf_set_changedtick(buf_T *const buf, assert(changedtick_di == (dictitem_T *)&buf->changedtick_di); #endif buf->changedtick_di.di_tv.vval.v_number = changedtick; + + if (tv_dict_is_watched(buf->b_vars)) { + tv_dict_watcher_notify(buf->b_vars, + (char *)buf->changedtick_di.di_key, + &buf->changedtick_di.di_tv, + &old_val); + } } static inline varnumber_T buf_get_changedtick(const buf_T *const buf) diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 48cef9b1e7..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" */ @@ -778,7 +779,9 @@ struct file_buffer { * normally points to this, but some windows * may use a different synblock_T. */ - signlist_T *b_signlist; /* list of signs to draw */ + signlist_T *b_signlist; // list of signs to draw + int b_signcols_max; // cached maximum number of sign columns + int b_signcols; // last calculated number of sign columns Terminal *terminal; // Terminal instance associated with the buffer @@ -958,24 +961,32 @@ struct matchitem { int conceal_char; ///< cchar for Conceal highlighting }; -typedef enum { - kFloatAnchorEast = 1, - kFloatAnchorSouth = 2, +typedef int FloatAnchor; +typedef int FloatRelative; - kFloatAnchorNW = 0, - kFloatAnchorNE = 1, - kFloatAnchorSW = 2, - kFloatAnchorSE = 3, -} FloatAnchor; +enum { + kFloatAnchorEast = 1, + kFloatAnchorSouth = 2, +}; + +// NW -> 0 +// NE -> kFloatAnchorEast +// SW -> kFloatAnchorSouth +// SE -> kFloatAnchorSouth | kFloatAnchorEast +EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" }); + +enum { + kFloatRelativeEditor = 0, + kFloatRelativeWindow = 1, + kFloatRelativeCursor = 2, +}; -typedef enum { - kFloatRelativeEditor = 0, - kFloatRelativeWindow = 1, - kFloatRelativeCursor = 2, -} FloatRelative; +EXTERN const char *const float_relative_str[] INIT(= { "editor", "window", + "cursor" }); typedef struct { Window window; + int height, width; double row, col; FloatAnchor anchor; FloatRelative relative; @@ -983,22 +994,31 @@ typedef struct { bool focusable; } FloatConfig; -#define FLOAT_CONFIG_INIT ((FloatConfig){ .row = 0, .col = 0, .anchor = 0, \ +#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ + .row = 0, .col = 0, .anchor = 0, \ .relative = 0, .external = false, \ .focusable = true }) -/* - * Structure which contains all information that belongs to a window - * - * All row numbers are relative to the start of the window, except w_winrow. - */ +// 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. struct window_S { handle_T handle; ///< unique identifier for the window buf_T *w_buffer; ///< buffer we are a window into (used ///< often, keep it the first item!) - synblock_T *w_s; /* for :ownsyntax */ + synblock_T *w_s; ///< for :ownsyntax int w_hl_id_normal; ///< 'winhighlight' normal id int w_hl_attr_normal; ///< 'winhighlight' normal final attrs @@ -1008,24 +1028,25 @@ struct window_S { int w_hl_needs_update; ///< attrs need to be recalculated - win_T *w_prev; /* link to previous window */ - win_T *w_next; /* link to next window */ - bool w_closing; /* window is being closed, don't let - autocommands close it too. */ + win_T *w_prev; ///< link to previous window + win_T *w_next; ///< link to next window + bool w_closing; ///< window is being closed, don't let + /// autocommands close it too. - frame_T *w_frame; /* frame containing this window */ + frame_T *w_frame; ///< frame containing this window - pos_T w_cursor; /* cursor position in buffer */ + pos_T w_cursor; ///< cursor position in buffer - colnr_T w_curswant; /* The column we'd like to be at. This is - used to try to stay in the same column - for up/down cursor motions. */ + colnr_T w_curswant; ///< Column we want to be at. This is + /// used to try to stay in the same column + /// for up/down cursor motions. int w_set_curswant; // If set, then update w_curswant the next // time through cursupdate() to the // current virtual column linenr_T w_last_cursorline; ///< where last 'cursorline' was drawn + pos_T w_last_cursormoved; ///< for CursorMoved event // the next seven are used to update the visual part char w_old_visual_mode; ///< last known VIsual_mode @@ -1080,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/charset.c b/src/nvim/charset.c index ddff93d83b..b155b3861f 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1777,9 +1777,12 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ + const uvarnumber_T digit = (uvarnumber_T)(conv); \ /* avoid ubsan error for overflow */ \ - if (un < UVARNUMBER_MAX / base) { \ - un = base * un + (uvarnumber_T)(conv); \ + if (un < UVARNUMBER_MAX / base \ + || (un == UVARNUMBER_MAX / base \ + && (base != 10 || digit <= UVARNUMBER_MAX % 10))) { \ + un = base * un + digit; \ } else { \ un = UVARNUMBER_MAX; \ } \ diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 667bc54e2e..49bc2ab2f0 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -58,9 +58,10 @@ #include "nvim/os/input.h" #include "nvim/os/time.h" -/* - * definitions used for CTRL-X submode - */ +// Definitions used for CTRL-X submode. +// Note: If you change CTRL-X submode, you must also maintain ctrl_x_msgs[] +// and ctrl_x_mode_names[]. + #define CTRL_X_WANT_IDENT 0x100 #define CTRL_X_NOT_DEFINED_YET 1 @@ -83,17 +84,18 @@ #define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] #define CTRL_X_MODE_LINE_OR_EVAL(m) (m == CTRL_X_WHOLE_LINE || m == CTRL_X_EVAL) +// Message for CTRL-X mode, index is ctrl_x_mode. static char *ctrl_x_msgs[] = { - N_(" Keyword completion (^N^P)"), /* ctrl_x_mode == 0, ^P/^N compl. */ + N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), - NULL, + NULL, // CTRL_X_SCROLL: depends on state N_(" Whole line completion (^L^N^P)"), N_(" File name completion (^F^N^P)"), N_(" Tag completion (^]^N^P)"), N_(" Path pattern completion (^N^P)"), N_(" Definition completion (^D^N^P)"), - NULL, + NULL, // CTRL_X_FINISHED N_(" Dictionary completion (^K^N^P)"), N_(" Thesaurus completion (^T^N^P)"), N_(" Command-line completion (^V^N^P)"), @@ -104,6 +106,26 @@ static char *ctrl_x_msgs[] = NULL, // CTRL_X_EVAL doesn't use msg. }; +static char *ctrl_x_mode_names[] = { + "keyword", + "ctrl_x", + "unknown", // CTRL_X_SCROLL + "whole_line", + "files", + "tags", + "path_patterns", + "path_defines", + "unknown", // CTRL_X_FINISHED + "dictionary", + "thesaurus", + "cmdline", + "function", + "omni", + "spell", + NULL, // CTRL_X_LOCAL_MSG only used in "ctrl_x_msgs" + "eval" +}; + static char e_hitend[] = N_("Hit end of paragraph"); static char e_complwin[] = N_("E839: Completion function changed window"); static char e_compldel[] = N_("E840: Completion function deleted text"); @@ -115,16 +137,18 @@ typedef struct compl_S compl_T; struct compl_S { compl_T *cp_next; compl_T *cp_prev; - char_u *cp_str; /* matched text */ - char cp_icase; /* TRUE or FALSE: ignore case */ - char_u *(cp_text[CPT_COUNT]); /* text for the menu */ - char_u *cp_fname; /* file containing the match, allocated when - * cp_flags has FREE_FNAME */ - int cp_flags; /* ORIGINAL_TEXT, CONT_S_IPOS or FREE_FNAME */ - int cp_number; /* sequence number */ + char_u *cp_str; // matched text + char cp_icase; // TRUE or FALSE: ignore case + char cp_equal; // TRUE or FALSE: ins_compl_equal always ok + char_u *(cp_text[CPT_COUNT]); // text for the menu + char_u *cp_fname; // file containing the match, allocated when + // cp_flags has FREE_FNAME + int cp_flags; // ORIGINAL_TEXT, CONT_S_IPOS or FREE_FNAME + int cp_number; // sequence number }; -#define ORIGINAL_TEXT (1) /* the original text when the expansion begun */ +// flags for ins_compl_add() +#define ORIGINAL_TEXT (1) // the original text when the expansion begun #define FREE_FNAME (2) /* @@ -645,8 +669,9 @@ static int insert_execute(VimState *state, int key) // there is nothing to add, CTRL-L works like CTRL-P then. if (s->c == Ctrl_L && (!CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode) - || (int)STRLEN(compl_shown_match->cp_str) - > curwin->w_cursor.col - compl_col)) { + || (compl_shown_match->cp_str != NULL + && (int)STRLEN(compl_shown_match->cp_str) + > curwin->w_cursor.col - compl_col))) { ins_compl_addfrommatch(); return 1; // continue } @@ -1376,7 +1401,7 @@ ins_redraw ( // Trigger CursorMoved if the cursor moved. Not when the popup menu is // visible, the command might delete it. if (ready && (has_event(EVENT_CURSORMOVEDI) || curwin->w_p_cole > 0) - && !equalpos(last_cursormoved, curwin->w_cursor) + && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor) && !pum_visible()) { // Need to update the screen first, to make sure syntax // highlighting is correct after making a change (e.g., inserting @@ -1392,7 +1417,7 @@ ins_redraw ( ins_apply_autocmds(EVENT_CURSORMOVEDI); } conceal_cursor_moved = true; - last_cursormoved = curwin->w_cursor; + curwin->w_last_cursormoved = curwin->w_cursor; } // Trigger TextChangedI if changedtick differs. @@ -2012,14 +2037,14 @@ static bool ins_compl_accept_char(int c) return vim_iswordc(c); } -/* - * This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the - * case of the originally typed text is used, and the case of the completed - * text is inferred, ie this tries to work out what case you probably wanted - * the rest of the word to be in -- webb - */ -int ins_compl_add_infercase(char_u *str, int len, int icase, char_u *fname, int dir, int flags) +// This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the +// case of the originally typed text is used, and the case of the completed +// text is inferred, ie this tries to work out what case you probably wanted +// the rest of the word to be in -- webb +int ins_compl_add_infercase(char_u *str_arg, int len, int icase, char_u *fname, + int dir, int flags) { + char_u *str = str_arg; int i, c; int actual_len; /* Take multi-byte characters */ int actual_compl_length; /* into account. */ @@ -2148,10 +2173,10 @@ int ins_compl_add_infercase(char_u *str, int len, int icase, char_u *fname, int xfree(wca); - return ins_compl_add(IObuff, len, icase, fname, NULL, false, dir, flags, - false); + str = IObuff; } - return ins_compl_add(str, len, icase, fname, NULL, false, dir, flags, false); + return ins_compl_add(str, len, icase, fname, NULL, false, dir, flags, + false, false); } /// Add a match to the list of matches @@ -2168,6 +2193,7 @@ int ins_compl_add_infercase(char_u *str, int len, int icase, char_u *fname, int /// cptext itself will not be freed. /// @param[in] cdir Completion direction. /// @param[in] adup True if duplicate matches are to be accepted. +/// @param[in] equal Match is always accepted by ins_compl_equal. /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be @@ -2176,7 +2202,8 @@ static int ins_compl_add(char_u *const str, int len, const bool icase, char_u *const fname, char_u *const *const cptext, const bool cptext_allocated, - const Direction cdir, int flags, const bool adup) + const Direction cdir, int flags, const bool adup, + int equal) FUNC_ATTR_NONNULL_ARG(1) { compl_T *match; @@ -2228,6 +2255,7 @@ static int ins_compl_add(char_u *const str, int len, match->cp_number = 0; match->cp_str = vim_strnsave(str, len); match->cp_icase = icase; + match->cp_equal = equal; /* match-fname is: * - compl_curr_match->cp_fname if it is a string equal to fname. @@ -2301,6 +2329,9 @@ static int ins_compl_add(char_u *const str, int len, static bool ins_compl_equal(compl_T *match, char_u *str, size_t len) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { + if (match->cp_equal) { + return true; + } if (match->cp_icase) { return STRNICMP(match->cp_str, str, len) == 0; } @@ -2374,7 +2405,8 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase) for (i = 0; i < num_matches && add_r != FAIL; i++) if ((add_r = ins_compl_add(matches[i], -1, icase, - NULL, NULL, false, dir, 0, false)) == OK) { + NULL, NULL, false, dir, 0, false, + false)) == OK) { // If dir was BACKWARD then honor it just once. dir = FORWARD; } @@ -2443,7 +2475,7 @@ void set_completion(colnr_T startcol, list_T *list) compl_orig_text = vim_strnsave(get_cursor_line_ptr() + compl_col, compl_length); if (ins_compl_add(compl_orig_text, -1, p_ic, NULL, NULL, false, 0, - ORIGINAL_TEXT, false) != OK) { + ORIGINAL_TEXT, false, false) != OK) { return; } @@ -2656,8 +2688,25 @@ void ins_compl_show_pum(void) col = curwin->w_cursor.col; curwin->w_cursor.col = compl_col; pum_selected_item = cur; - pum_display(compl_match_array, compl_match_arraysize, cur, array_changed); + pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0); curwin->w_cursor.col = col; + + if (!has_event(EVENT_COMPLETECHANGED)) { + return; + } + dict_T *dict = get_vim_var_dict(VV_EVENT); + if (cur < 0) { + tv_dict_add_dict(dict, S_LEN("completed_item"), tv_dict_alloc()); + } else { + dict_T *item = ins_compl_dict_alloc(compl_curr_match); + tv_dict_add_dict(dict, S_LEN("completed_item"), item); + } + pum_set_boundings(dict); + tv_dict_set_keys_readonly(dict); + textlock++; + apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf); + textlock--; + tv_dict_clear(dict); } #define DICT_FIRST (1) /* use just first element in "dict" */ @@ -2963,6 +3012,99 @@ bool ins_compl_active(void) return compl_started; } +// Get complete information +void get_complete_info(list_T *what_list, dict_T *retdict) +{ +#define CI_WHAT_MODE 0x01 +#define CI_WHAT_PUM_VISIBLE 0x02 +#define CI_WHAT_ITEMS 0x04 +#define CI_WHAT_SELECTED 0x08 +#define CI_WHAT_INSERTED 0x10 +#define CI_WHAT_ALL 0xff + int what_flag; + + if (what_list == NULL) { + what_flag = CI_WHAT_ALL; + } else { + what_flag = 0; + for (listitem_T *item = tv_list_first(what_list) + ; item != NULL + ; item = TV_LIST_ITEM_NEXT(what_list, item)) { + const char *what = tv_get_string(TV_LIST_ITEM_TV(item)); + + if (STRCMP(what, "mode") == 0) { + what_flag |= CI_WHAT_MODE; + } else if (STRCMP(what, "pum_visible") == 0) { + what_flag |= CI_WHAT_PUM_VISIBLE; + } else if (STRCMP(what, "items") == 0) { + what_flag |= CI_WHAT_ITEMS; + } else if (STRCMP(what, "selected") == 0) { + what_flag |= CI_WHAT_SELECTED; + } else if (STRCMP(what, "inserted") == 0) { + what_flag |= CI_WHAT_INSERTED; + } + } + } + + int ret = OK; + if (what_flag & CI_WHAT_MODE) { + ret = tv_dict_add_str(retdict, S_LEN("mode"), + (char *)ins_compl_mode()); + } + + if (ret == OK && (what_flag & CI_WHAT_PUM_VISIBLE)) { + ret = tv_dict_add_nr(retdict, S_LEN("pum_visible"), pum_visible()); + } + + if (ret == OK && (what_flag & CI_WHAT_ITEMS)) { + list_T *li = tv_list_alloc(ins_compl_len()); + + ret = tv_dict_add_list(retdict, S_LEN("items"), li); + if (ret == OK && compl_first_match != NULL) { + compl_T *match = compl_first_match; + do { + if (!(match->cp_flags & ORIGINAL_TEXT)) { + dict_T *di = tv_dict_alloc(); + + tv_list_append_dict(li, di); + tv_dict_add_str(di, S_LEN("word"), + (char *)EMPTY_IF_NULL(match->cp_str)); + tv_dict_add_str(di, S_LEN("abbr"), + (char *)EMPTY_IF_NULL(match->cp_text[CPT_ABBR])); + tv_dict_add_str(di, S_LEN("menu"), + (char *)EMPTY_IF_NULL(match->cp_text[CPT_MENU])); + tv_dict_add_str(di, S_LEN("kind"), + (char *)EMPTY_IF_NULL(match->cp_text[CPT_KIND])); + tv_dict_add_str(di, S_LEN("info"), + (char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO])); + tv_dict_add_str(di, S_LEN("user_data"), + (char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA])); + } + match = match->cp_next; + } while (match != NULL && match != compl_first_match); + } + } + + if (ret == OK && (what_flag & CI_WHAT_SELECTED)) { + ret = tv_dict_add_nr(retdict, S_LEN("selected"), + (compl_curr_match != NULL) + ? compl_curr_match->cp_number - 1 : -1); + } + + // TODO(vim): + // if (ret == OK && (what_flag & CI_WHAT_INSERTED)) +} + +// Return Insert completion mode name string +static char_u * ins_compl_mode(void) +{ + if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET || compl_started) { + return (char_u *)ctrl_x_mode_names[ctrl_x_mode & ~CTRL_X_WANT_IDENT]; + } + return (char_u *)""; +} + + /* * Delete one character before the cursor and show the subset of the matches * that match the word that is now before the cursor. @@ -3626,6 +3768,7 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) bool icase = false; bool adup = false; bool aempty = false; + bool aequal = false; char *(cptext[CPT_COUNT]); if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) { @@ -3640,6 +3783,9 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) icase = (bool)tv_dict_get_number(tv->vval.v_dict, "icase"); adup = (bool)tv_dict_get_number(tv->vval.v_dict, "dup"); aempty = (bool)tv_dict_get_number(tv->vval.v_dict, "empty"); + if (tv_dict_get_string(tv->vval.v_dict, "equal", false) != NULL) { + aequal = tv_dict_get_number(tv->vval.v_dict, "equal"); + } } else { word = (const char *)tv_get_string_chk(tv); memset(cptext, 0, sizeof(cptext)); @@ -3651,7 +3797,7 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) return FAIL; } return ins_compl_add((char_u *)word, -1, icase, NULL, - (char_u **)cptext, true, dir, 0, adup); + (char_u **)cptext, true, dir, 0, adup, aequal); } /* @@ -4097,31 +4243,37 @@ static void ins_compl_insert(int in_compl_func) else compl_used_match = TRUE; - // Set completed item. + dict_T *dict = ins_compl_dict_alloc(compl_shown_match); + set_vim_var_dict(VV_COMPLETED_ITEM, dict); + if (!in_compl_func) { + compl_curr_match = compl_shown_match; + } +} + +// Convert to complete item dict +static dict_T *ins_compl_dict_alloc(compl_T *match) +{ // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); tv_dict_add_str( dict, S_LEN("word"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_str)); + (const char *)EMPTY_IF_NULL(match->cp_str)); tv_dict_add_str( dict, S_LEN("abbr"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_ABBR])); + (const char *)EMPTY_IF_NULL(match->cp_text[CPT_ABBR])); tv_dict_add_str( dict, S_LEN("menu"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_MENU])); + (const char *)EMPTY_IF_NULL(match->cp_text[CPT_MENU])); tv_dict_add_str( dict, S_LEN("kind"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_KIND])); + (const char *)EMPTY_IF_NULL(match->cp_text[CPT_KIND])); tv_dict_add_str( dict, S_LEN("info"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_INFO])); + (const char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO])); tv_dict_add_str( dict, S_LEN("user_data"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_USER_DATA])); - set_vim_var_dict(VV_COMPLETED_ITEM, dict); - if (!in_compl_func) { - compl_curr_match = compl_shown_match; - } + (const char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA])); + return dict; } /* @@ -4807,7 +4959,7 @@ static int ins_complete(int c, bool enable_pum) xfree(compl_orig_text); compl_orig_text = vim_strnsave(line + compl_col, compl_length); if (ins_compl_add(compl_orig_text, -1, p_ic, NULL, NULL, false, 0, - ORIGINAL_TEXT, false) != OK) { + ORIGINAL_TEXT, false, false) != OK) { xfree(compl_pattern); compl_pattern = NULL; xfree(compl_orig_text); @@ -5486,16 +5638,33 @@ internal_format ( /* remember position of blank just before text */ end_col = curwin->w_cursor.col; - /* find start of sequence of blanks */ + // find start of sequence of blanks + int wcc = 0; // counter for whitespace chars while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { dec_cursor(); cc = gchar_cursor(); + + // Increment count of how many whitespace chars in this + // group; we only need to know if it's more than one. + if (wcc < 2) { + wcc++; + } } - if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) - break; /* only spaces in front of text */ - /* Don't break until after the comment leader */ - if (curwin->w_cursor.col < leader_len) + if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { + break; // only spaces in front of text + } + + // Don't break after a period when 'formatoptions' has 'p' and + // there are less than two spaces. + if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { + continue; + } + + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { break; + } + if (has_format_option(FO_ONE_LETTER)) { /* do not break after one-letter words */ if (curwin->w_cursor.col == 0) @@ -5859,10 +6028,7 @@ comp_textwidth ( textwidth -= 1; } textwidth -= curwin->w_p_fdc; - - if (signcolumn_on(curwin)) { - textwidth -= 1; - } + textwidth -= win_signcol_count(curwin); if (curwin->w_p_nu || curwin->w_p_rnu) textwidth -= 8; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5ef2a8772e..6479163028 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -241,13 +241,14 @@ typedef enum { ///< the value (prevents error message). } GetLvalFlags; -// function flags +// flags used in uf_flags #define FC_ABORT 0x01 // abort function on error #define FC_RANGE 0x02 // function accepts range #define FC_DICT 0x04 // Dict function, uses "self" #define FC_CLOSURE 0x08 // closure, uses outer scope variables #define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 #define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 +#define FC_SANDBOX 0x40 // function defined in the sandbox // The names of packages that once were loaded are remembered. static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL }; @@ -448,6 +449,7 @@ typedef struct { int timer_id; int repeat_count; int refcount; + int emsg_count; ///< Errors in a repeating timer. long timeout; bool stopped; bool paused; @@ -3933,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; /* @@ -5219,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 @@ -5853,6 +5855,9 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) if (prof_def_func()) { func_do_profile(fp); } + if (sandbox) { + flags |= FC_SANDBOX; + } fp->uf_varargs = true; fp->uf_flags = flags; fp->uf_calls = 0; @@ -6328,8 +6333,12 @@ call_func( } - /* execute the function if no errors detected and executing */ - if (evaluate && error == ERROR_NONE) { + // Execute the function if executing and no errors were detected. + if (!evaluate) { + // Not evaluating, which means the return value is unknown. This + // matters for giving error messages. + rettv->v_type = VAR_UNKNOWN; + } else if (error == ERROR_NONE) { char_u *rfname = fname; /* Ignore "g:" before a function name. */ @@ -6654,12 +6663,24 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; /* Failed */ } -/* - * "argc()" function - */ static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = ARGCOUNT; + if (argvars[0].v_type == VAR_UNKNOWN) { + // use the current window + rettv->vval.v_number = ARGCOUNT; + } else if (argvars[0].v_type == VAR_NUMBER + && tv_get_number(&argvars[0]) == -1) { + // use the global argument list + rettv->vval.v_number = GARGCOUNT; + } else { + // use the argument list of the specified window + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + rettv->vval.v_number = WARGCOUNT(wp); + } else { + rettv->vval.v_number = -1; + } + } } /* @@ -6680,28 +6701,54 @@ static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// Get the argument list for a given window +static void get_arglist_as_rettv(aentry_T *arglist, int argcount, + typval_T *rettv) +{ + tv_list_alloc_ret(rettv, argcount); + if (arglist != NULL) { + for (int idx = 0; idx < argcount; idx++) { + tv_list_append_string(rettv->vval.v_list, + (const char *)alist_name(&arglist[idx]), -1); + } + } +} + /* * "argv(nr)" function */ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int idx; + aentry_T *arglist = NULL; + int argcount = -1; if (argvars[0].v_type != VAR_UNKNOWN) { - idx = (int)tv_get_number_chk(&argvars[0], NULL); - if (idx >= 0 && idx < ARGCOUNT) { - rettv->vval.v_string = (char_u *)xstrdup( - (const char *)alist_name(&ARGLIST[idx])); + if (argvars[1].v_type == VAR_UNKNOWN) { + arglist = ARGLIST; + argcount = ARGCOUNT; + } else if (argvars[1].v_type == VAR_NUMBER + && tv_get_number(&argvars[1]) == -1) { + arglist = GARGLIST; + argcount = GARGCOUNT; } else { - rettv->vval.v_string = NULL; + win_T *wp = find_win_by_nr_or_id(&argvars[1]); + if (wp != NULL) { + // Use the argument list of the specified window + arglist = WARGLIST(wp); + argcount = WARGCOUNT(wp); + } } rettv->v_type = VAR_STRING; - } else { - tv_list_alloc_ret(rettv, ARGCOUNT); - for (idx = 0; idx < ARGCOUNT; idx++) { - tv_list_append_string(rettv->vval.v_list, - (const char *)alist_name(&ARGLIST[idx]), -1); + rettv->vval.v_string = NULL; + int idx = tv_get_number_chk(&argvars[0], NULL); + if (arglist != NULL && idx >= 0 && idx < argcount) { + rettv->vval.v_string = (char_u *)xstrdup( + (const char *)alist_name(&arglist[idx])); + } else if (idx == -1) { + get_arglist_as_rettv(arglist, argcount, rettv); } + } else { + get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); } } @@ -6830,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) { @@ -7556,6 +7624,23 @@ static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) RedrawingDisabled = saved; } +// "complete_info()" function +static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + list_T *what_list = NULL; + + if (argvars[0].v_type != VAR_UNKNOWN) { + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + what_list = argvars[0].vval.v_list; + } + get_complete_info(what_list, rettv->vval.v_dict); +} + /* * "confirm(message, buttons[, default [, type]])" function */ @@ -7983,7 +8068,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlID = HLF_CHD; // Changed line. } } - rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)hlID; + rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); } /* @@ -8194,6 +8279,19 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = path; } +/// Find a window: When using a Window ID in any tab page, when using a number +/// in the current tab page. +win_T * find_win_by_nr_or_id(typval_T *vp) +{ + int nr = (int)tv_get_number_chk(vp, NULL); + + if (nr >= LOWEST_WIN_ID) { + return win_id2wp(vp); + } + + return find_win_by_nr(vp, NULL); +} + /* * "exists()" function */ @@ -9953,6 +10051,37 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = type; } +// "getjumplist()" function +static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); + if (wp == NULL) { + return; + } + + cleanup_jumplist(wp, true); + + list_T *const l = tv_list_alloc(wp->w_jumplistlen); + tv_list_append_list(rettv->vval.v_list, l); + tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx); + + for (int i = 0; i < wp->w_jumplistlen; i++) { + if (wp->w_jumplist[i].fmark.mark.lnum == 0) { + continue; + } + dict_T *const d = tv_dict_alloc(); + tv_list_append_dict(l, d); + tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum); + tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col); + tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd); + tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum); + if (wp->w_jumplist[i].fname != NULL) { + tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname); + } + } +} + /* * "getline(lnum, [end])" function */ @@ -9988,7 +10117,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, dict_T *d = what_arg->vval.v_dict; if (d != NULL) { - get_errorlist_properties(wp, d, rettv->vval.v_dict); + qf_get_properties(wp, d, rettv->vval.v_dict); } } else { EMSG(_(e_dictreq)); @@ -10000,7 +10129,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, /// "getloclist()" function static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp = find_win_by_nr(&argvars[0], NULL); + win_T *wp = find_win_by_nr_or_id(&argvars[0]); get_qf_loc_list(false, wp, &argvars[1], rettv); } @@ -10019,7 +10148,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) { @@ -10306,6 +10435,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) { @@ -10371,11 +10517,19 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); - const win_T *const wp = find_win_by_nr(&argvars[0], NULL); + const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); 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 */ @@ -10707,6 +10861,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef __APPLE__ "mac", "macunix", + "osx", #endif "menu", "mksession", @@ -11180,7 +11335,6 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) static int inputsecret_flag = 0; - /* * This function is used by f_input() and f_inputdialog() functions. The third * argument to f_input() specifies the type of completion to use at the @@ -11369,25 +11523,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. @@ -11395,9 +11545,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++; @@ -11564,10 +11712,11 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { - const int len = vim_vsnprintf(NULL, 0, "%p", dummy_ap, argvars); + const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); rettv->v_type = VAR_STRING; rettv->vval.v_string = xmalloc(len + 1); - vim_vsnprintf((char *)rettv->vval.v_string, len + 1, "%p", dummy_ap, argvars); + vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p", + dummy_ap, argvars); } /* @@ -12503,6 +12652,31 @@ static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) find_some_match(argvars, rettv, kSomeMatch); } +static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, + win_T **win) +{ + dictitem_T *di; + + if (tv->v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return FAIL; + } + + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) { + *conceal_char = tv_get_string(&di->di_tv); + } + + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) { + *win = find_win_by_nr_or_id(&di->di_tv); + if (*win == NULL) { + EMSG(_("E957: Invalid window number")); + return FAIL; + } + } + + return OK; +} + /* * "matchadd()" function */ @@ -12516,6 +12690,7 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) int id = -1; bool error = false; const char *conceal_char = NULL; + win_T *win = curwin; rettv->vval.v_number = -1; @@ -12526,16 +12701,9 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) prio = tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - dictitem_T *di; - if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) - != NULL) { - conceal_char = tv_get_string(&di->di_tv); - } + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; } } } @@ -12547,8 +12715,7 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_number = match_add(curwin, grp, pat, prio, id, NULL, - conceal_char); + rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); } static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -12576,21 +12743,15 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) int prio = 10; int id = -1; const char *conceal_char = NULL; + win_T *win = curwin; if (argvars[2].v_type != VAR_UNKNOWN) { prio = tv_get_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - dictitem_T *di; - if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) - != NULL) { - conceal_char = tv_get_string(&di->di_tv); - } + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; } } } @@ -12604,8 +12765,7 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l, - conceal_char); + rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); } /* @@ -12752,33 +12912,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 @@ -13020,11 +13183,11 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) did_emsg = false; char buf[NUMBUFLEN]; const char *fmt = tv_get_string_buf(&argvars[0], buf); - len = vim_vsnprintf(NULL, 0, fmt, dummy_ap, argvars + 1); + len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1); if (!did_emsg) { char *s = xmalloc(len + 1); rettv->vval.v_string = (char_u *)s; - (void)vim_vsnprintf(s, len + 1, fmt, dummy_ap, argvars + 1); + (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1); } did_emsg |= saved_did_emsg; } @@ -14761,7 +14924,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++; @@ -14781,7 +14944,7 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = -1; - win = find_win_by_nr(&argvars[0], NULL); + win = find_win_by_nr_or_id(&argvars[0]); if (win != NULL) { set_qf_ll_list(win, &argvars[1], rettv); } @@ -14837,7 +15000,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) { @@ -15087,6 +15250,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 */ @@ -16306,6 +16523,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) { @@ -16631,7 +16869,6 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - /* * "tabpagenr()" function */ @@ -16668,6 +16905,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) twin = (tp == curtab) ? curwin : tp->tp_curwin; if (argvar->v_type != VAR_UNKNOWN) { + bool invalid_arg = false; const char *const arg = tv_get_string_chk(argvar); if (arg == NULL) { nr = 0; // Type error; errmsg already given. @@ -16679,6 +16917,31 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) nr = 0; } } else { + // Extract the window count (if specified). e.g. winnr('3j') + char_u *endp; + long count = strtol((char *)arg, (char **)&endp, 10); + if (count <= 0) { + // if count is not specified, default to 1 + count = 1; + } + if (endp != NULL && *endp != '\0') { + if (strequal((char *)endp, "j")) { + twin = win_vert_neighbor(tp, twin, false, count); + } else if (strequal((char *)endp, "k")) { + twin = win_vert_neighbor(tp, twin, true, count); + } else if (strequal((char *)endp, "h")) { + twin = win_horz_neighbor(tp, twin, true, count); + } else if (strequal((char *)endp, "l")) { + twin = win_horz_neighbor(tp, twin, false, count); + } else { + invalid_arg = true; + } + } else { + invalid_arg = true; + } + } + + if (invalid_arg) { EMSG2(_(e_invexpr2), arg); nr = 0; } @@ -16712,7 +16975,6 @@ static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = nr; } - /* * "tagfiles()" function */ @@ -17075,6 +17337,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) timer->refcount = 1; timer->stopped = false; timer->paused = false; + timer->emsg_count = 0; timer->repeat_count = repeat; timer->timeout = timeout; timer->timer_id = last_timer_id++; @@ -17117,6 +17380,9 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) static void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; + int save_did_emsg = did_emsg; + int save_called_emsg = called_emsg; + if (timer->stopped || timer->paused) { return; } @@ -17131,8 +17397,24 @@ static void timer_due_cb(TimeWatcher *tw, void *data) argv[0].v_type = VAR_NUMBER; argv[0].vval.v_number = timer->timer_id; typval_T rettv = TV_INITIAL_VALUE; + called_emsg = false; callback_call(&timer->callback, 1, argv, &rettv); + + // Handle error message + if (called_emsg && did_emsg) { + timer->emsg_count++; + if (current_exception != NULL) { + discard_current_exception(); + } + } + did_emsg = save_did_emsg; + called_emsg = save_called_emsg; + + if (timer->emsg_count >= 3) { + timer_stop(timer); + } + tv_clear(&rettv); if (!timer->stopped && timer->timeout == 0) { @@ -17419,7 +17701,7 @@ static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // If there is no file name there will be no undo file. rettv->vval.v_string = NULL; } else { - char *ffname = FullName_save(fname, false); + char *ffname = FullName_save(fname, true); if (ffname != NULL) { rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false); @@ -17532,18 +17814,15 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = win_id2win(argvars); } -/* - * "winbufnr(nr)" function - */ +/// "winbufnr(nr)" function static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp; - - wp = find_win_by_nr(&argvars[0], NULL); - if (wp == NULL) + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { rettv->vval.v_number = -1; - else + } else { rettv->vval.v_number = wp->w_buffer->b_fnum; + } } /* @@ -17555,18 +17834,15 @@ static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = curwin->w_wcol + 1; } -/* - * "winheight(nr)" function - */ +/// "winheight(nr)" function static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp; - - wp = find_win_by_nr(&argvars[0], NULL); - if (wp == NULL) + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { rettv->vval.v_number = -1; - else + } else { rettv->vval.v_number = wp->w_height; + } } /* @@ -17830,18 +18106,15 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) return ret; } -/* - * "winwidth(nr)" function - */ +/// "winwidth(nr)" function static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp; - - wp = find_win_by_nr(&argvars[0], NULL); - if (wp == NULL) + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { rettv->vval.v_number = -1; - else + } else { rettv->vval.v_number = wp->w_width; + } } /// "wordcount()" function @@ -18053,7 +18326,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; @@ -19570,16 +19843,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; @@ -19623,14 +19895,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(); + } } } @@ -20348,6 +20620,9 @@ void ex_function(exarg_T *eap) if (prof_def_func()) func_do_profile(fp); fp->uf_varargs = varargs; + if (sandbox) { + flags |= FC_SANDBOX; + } fp->uf_flags = flags; fp->uf_calls = 0; fp->uf_script_ID = current_SID; @@ -21338,6 +21613,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, char_u *save_sourcing_name; linenr_T save_sourcing_lnum; scid_T save_current_SID; + bool using_sandbox = false; funccall_T *fc; int save_did_emsg; static int depth = 0; @@ -21495,6 +21771,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, save_sourcing_name = sourcing_name; save_sourcing_lnum = sourcing_lnum; sourcing_lnum = 1; + + if (fp->uf_flags & FC_SANDBOX) { + using_sandbox = true; + sandbox++; + } + // need space for new sourcing_name: // * save_sourcing_name // * "["number"].." or "function " @@ -21655,6 +21937,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (do_profiling_yes) { script_prof_restore(&wait_start); } + if (using_sandbox) { + sandbox--; + } if (p_verbose >= 12 && sourcing_name != NULL) { ++no_wait_return; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 78cac4878d..d4bb69613e 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}}, @@ -64,6 +65,7 @@ return { complete={args=2}, complete_add={args=1}, complete_check={}, + complete_info={args={0, 1}}, confirm={args={1, 4}}, copy={args=1}, cos={args=1, func="float_op_wrapper", data="&cos"}, @@ -129,6 +131,7 @@ return { getfsize={args=1}, getftime={args=1}, getftype={args=1}, + getjumplist={args={0, 2}}, getline={args={1, 2}}, getloclist={args={1, 2}}, getmatches={}, @@ -140,7 +143,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}}, @@ -266,6 +271,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}}, @@ -298,6 +304,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 e713323f5c..a5c33a66a2 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -13,7 +13,7 @@ typedef struct stream Stream; /// Type of function called when the Stream buffer is filled with data /// /// @param stream The Stream instance -/// @param rbuffer The associated RBuffer instance +/// @param buf The associated RBuffer instance /// @param count Number of bytes that was read. /// @param data User-defined data /// @param eof If the stream reached EOF. @@ -23,7 +23,7 @@ typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, /// Type of function called when the Stream has information about a write /// request. /// -/// @param wstream The Stream instance +/// @param stream The Stream instance /// @param data User-defined data /// @param status 0 on success, anything else indicates failure typedef void (*stream_write_cb)(Stream *stream, void *data, int status); @@ -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 4356767cc9..8436ac810e 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 */ @@ -3181,6 +3181,7 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, subflags->do_ask = false; subflags->do_error = true; subflags->do_print = false; + subflags->do_list = false; subflags->do_count = false; subflags->do_number = false; subflags->do_ic = kSubHonorOptions; @@ -3691,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; @@ -3819,7 +3819,6 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, if (!preview || has_second_delim) { if (subflags.do_count) { // prevent accidentally changing the buffer by a function - save_ma = curbuf->b_p_ma; curbuf->b_p_ma = false; sandbox++; } @@ -3830,9 +3829,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, sublen = vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, sub, sub_firstline, false, p_magic, true); + // If getting the substitute string caused an error, don't do + // the replacement. // Don't keep flags set by a recursive call subflags = subflags_save; - if (subflags.do_count) { + if (aborting() || subflags.do_count) { curbuf->b_p_ma = save_ma; if (sandbox > 0) { sandbox--; @@ -3982,8 +3983,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) { @@ -5147,10 +5149,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); } @@ -6385,6 +6388,7 @@ void ex_substitute(exarg_T *eap) } block_autocmds(); // Disable events during command preview. + input_disable_events(); char_u *save_eap = eap->arg; garray_T save_view; @@ -6427,6 +6431,7 @@ void ex_substitute(exarg_T *eap) restore_search_patterns(); win_size_restore(&save_view); ga_clear(&save_view); + input_enable_events(); unblock_autocmds(); } diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 4806eff1b4..0f69d476f9 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1004,7 +1004,7 @@ return { }, { command='function', - flags=bit.bor(EXTRA, BANG, CMDWIN), + flags=bit.bor(EXTRA, BANG, SBOXOK, CMDWIN), addr_type=ADDR_LINES, func='ex_function', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 4684a1b31d..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, @@ -1952,14 +1953,17 @@ void ex_next(exarg_T *eap) void ex_argedit(exarg_T *eap) { int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; + // Whether curbuf will be reused, curbuf->b_ffname will be set. + bool curbuf_is_reusable = curbuf_reusable(); if (do_arglist(eap->arg, AL_ADD, i) == FAIL) { return; } maketitle(); - if (curwin->w_arg_idx == 0 && (curbuf->b_ml.ml_flags & ML_EMPTY) - && curbuf->b_ffname == NULL) { + if (curwin->w_arg_idx == 0 + && (curbuf->b_ml.ml_flags & ML_EMPTY) + && (curbuf->b_ffname == NULL || curbuf_is_reusable)) { i = 0; } // Edit the argument. @@ -2257,7 +2261,8 @@ static int alist_add_list(int count, char_u **files, int after) } for (int i = 0; i < count; i++) { ARGLIST[after + i].ae_fname = files[i]; - ARGLIST[after + i].ae_fnum = buflist_add(files[i], BLN_LISTED); + ARGLIST[after + i].ae_fnum = buflist_add(files[i], + BLN_LISTED | BLN_CURBUF); } ALIST(curwin)->al_ga.ga_len += count; if (old_argcount > 0 && curwin->w_arg_idx >= after) { @@ -3399,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); @@ -3413,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 5b9b4fed12..9c4a3f389a 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -78,7 +78,7 @@ static int quitmore = 0; static int ex_pressedreturn = FALSE; -/* whether ":lcd" was produced for a session */ +/// Whether ":lcd" or ":tcd" was produced for a session. static int did_lcd; typedef struct ucmd { @@ -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 { @@ -2263,8 +2264,11 @@ static char_u * do_one_cmd(char_u **cmdlinep, need_rethrow = check_cstack = FALSE; doend: - if (curwin->w_cursor.lnum == 0) /* can happen with zero line number */ + // can happen with zero line number + if (curwin->w_cursor.lnum == 0) { curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + } if (errormsg != NULL && *errormsg != NUL && !did_emsg) { if (flags & DOCMD_VERBOSE) { @@ -2440,10 +2444,24 @@ static char_u *find_command(exarg_T *eap, int *full) } } - if (ASCII_ISLOWER(*eap->cmd)) - eap->cmdidx = cmdidxs[CharOrdLow(*eap->cmd)]; - else - eap->cmdidx = cmdidxs[26]; + if (ASCII_ISLOWER(eap->cmd[0])) { + const int c1 = eap->cmd[0]; + const int c2 = eap->cmd[1]; + + if (command_count != (int)CMD_SIZE) { + iemsg((char *)_("E943: Command table needs to be updated, run 'make'")); + getout(1); + } + + // Use a precomputed index for fast look-up in cmdnames[] + // taking into account the first 2 letters of eap->cmd. + eap->cmdidx = cmdidxs1[CharOrdLow(c1)]; + if (ASCII_ISLOWER(c2)) { + eap->cmdidx += cmdidxs2[CharOrdLow(c1)][CharOrdLow(c2)]; + } + } else { + eap->cmdidx = CMD_bang; + } for (; (int)eap->cmdidx < (int)CMD_SIZE; eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) @@ -3235,7 +3253,7 @@ const char * set_one_cmd_context( case CMD_tjump: case CMD_stjump: case CMD_ptjump: - if (*p_wop != NUL) { + if (wop_flags & WOP_TAGFILE) { xp->xp_context = EXPAND_TAGS_LISTFILES; } else { xp->xp_context = EXPAND_TAGS; @@ -4488,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; } @@ -6903,10 +6921,11 @@ static void ex_resize(exarg_T *eap) n = 9999; win_setwidth_win(n, wp); } else { - if (*eap->arg == '-' || *eap->arg == '+') + if (*eap->arg == '-' || *eap->arg == '+') { n += curwin->w_height; - else if (n == 0 && eap->arg[0] == NUL) /* default is very wide */ + } else if (n == 0 && eap->arg[0] == NUL) { // default is very high n = 9999; + } win_setheight_win(n, wp); } } @@ -7195,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); } } @@ -7224,7 +7244,7 @@ void free_cd_dir(void) /// Deal with the side effects of changing the current directory. /// /// @param scope Scope of the function call (global, tab or window). -void post_chdir(CdScope scope) +void post_chdir(CdScope scope, bool trigger_dirchanged) { // Always overwrite the window-local CWD. xfree(curwin->w_localdir); @@ -7264,7 +7284,10 @@ void post_chdir(CdScope scope) } shorten_fnames(true); - do_autocmd_dirchanged(cwd, scope); + + if (trigger_dirchanged) { + do_autocmd_dirchanged(cwd, scope); + } } /// `:cd`, `:tcd`, `:lcd`, `:chdir`, `:tchdir` and `:lchdir`. @@ -7323,10 +7346,10 @@ void ex_cd(exarg_T *eap) break; } - if (vim_chdir(new_dir, scope)) { + if (vim_chdir(new_dir)) { EMSG(_(e_failed)); } else { - post_chdir(scope); + post_chdir(scope, true); // Echo the new current directory if the command was typed. if (KeyTyped || p_verbose >= 5) { ex_pwd(eap); @@ -7787,11 +7810,12 @@ static void ex_redir(exarg_T *eap) redir_off = FALSE; } -/* - * ":redraw": force redraw - */ +/// ":redraw": force redraw static void ex_redraw(exarg_T *eap) { + if (State & CMDPREVIEW) { + return; // Ignore :redraw during 'inccommand' preview. #9777 + } int r = RedrawingDisabled; int p = p_lz; @@ -7820,11 +7844,12 @@ static void ex_redraw(exarg_T *eap) ui_flush(); } -/* - * ":redrawstatus": force redraw of status line(s) - */ +/// ":redrawstatus": force redraw of status line(s) static void ex_redrawstatus(exarg_T *eap) { + if (State & CMDPREVIEW) { + return; // Ignore :redrawstatus during 'inccommand' preview. #9777 + } int r = RedrawingDisabled; int p = p_lz; @@ -9130,6 +9155,7 @@ makeopens( || put_eol(fd) == FAIL) { return FAIL; } + did_lcd = true; } /* Don't continue in another tab page when doing only the current one diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 7f7851f078..5b2a1c5c51 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -569,10 +569,12 @@ static void discard_exception(except_T *excp, int was_finished) */ void discard_current_exception(void) { - discard_exception(current_exception, false); + if (current_exception != NULL) { + discard_exception(current_exception, false); + current_exception = NULL; + } // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. - current_exception = NULL; need_rethrow = false; } @@ -1766,6 +1768,7 @@ void enter_cleanup(cleanup_T *csp) */ if (current_exception || need_rethrow) { csp->exception = current_exception; + current_exception = NULL; } else { csp->exception = NULL; if (did_emsg) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 8e6fc5ad4f..b16023b0ec 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -214,6 +214,15 @@ static int hislen = 0; /* actual length of history tables */ /// user interrupting highlight function to not interrupt command-line. static bool getln_interrupted_highlight = false; +// "compl_match_array" points the currently displayed list of entries in the +// popup menu. It is NULL when there is no popup menu. +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; +// First column in cmdline of the matched item for completion. +static int compl_startcol; +static int compl_selected; + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" @@ -357,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(); } @@ -615,8 +624,10 @@ static int command_line_execute(VimState *state, int key) if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A && s->c != Ctrl_L) { - if (ui_has(kUIWildmenu)) { - ui_call_wildmenu_hide(); + if (compl_match_array) { + pum_undisplay(true); + xfree(compl_match_array); + compl_match_array = NULL; } if (s->xpc.xp_numfiles != -1) { (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); @@ -1378,6 +1389,7 @@ static int command_line_handle_key(CommandLineState *s) } } } + ccline.special_char = NUL; redrawcmd(); return command_line_changed(s); @@ -1436,6 +1448,9 @@ static int command_line_handle_key(CommandLineState *s) && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); set_cmdspos_cursor(); + if (ccline.special_char != NUL) { + putcmdline(ccline.special_char, ccline.special_shift); + } return command_line_not_changed(s); @@ -1693,6 +1708,7 @@ static int command_line_handle_key(CommandLineState *s) putcmdline('^', true); s->c = get_literal(); // get next (two) character(s) s->do_abbr = false; // don't do abbreviation now + ccline.special_char = NUL; // may need to remove ^ when composing char was typed if (enc_utf8 && utf_iscomposing(s->c) && !cmd_silent) { if (ui_has(kUICmdline)) { @@ -1710,6 +1726,7 @@ static int command_line_handle_key(CommandLineState *s) s->ignore_drag_release = true; putcmdline('?', true); s->c = get_digraph(true); + ccline.special_char = NUL; if (s->c != NUL) { break; @@ -3071,15 +3088,13 @@ void putcmdline(int c, int shift) draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); } msg_no_more = false; - } else { - ccline.special_char = c; - ccline.special_shift = shift; - if (ccline.redraw_state != kCmdRedrawAll) { + } else if (ccline.redraw_state != kCmdRedrawAll) { ui_call_cmdline_special_char(cchar_to_string((char)(c)), shift, ccline.level); - } } cursorcmd(); + ccline.special_char = c; + ccline.special_shift = shift; ui_cursor_shape(); } @@ -3097,6 +3112,7 @@ void unputcmdline(void) } msg_no_more = false; cursorcmd(); + ccline.special_char = NUL; ui_cursor_shape(); } @@ -3457,6 +3473,10 @@ void redrawcmd(void) set_cmdspos_cursor(); + if (ccline.special_char != NUL) { + putcmdline(ccline.special_char, ccline.special_shift); + } + /* * An emsg() before may have set msg_scroll. This is used in normal mode, * in cmdline mode we can reset them now. @@ -3746,13 +3766,12 @@ ExpandOne ( else findex = -1; } - if (p_wmnu) { - if (ui_has(kUIWildmenu)) { - ui_call_wildmenu_select(findex); - } else { - win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, - findex, cmd_showtail); - } + if (compl_match_array) { + compl_selected = findex; + cmdline_pum_display(false); + } else if (p_wmnu) { + win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, + findex, cmd_showtail); } if (findex == -1) { return vim_strsave(orig_save); @@ -4069,6 +4088,12 @@ void tilde_replace(char_u *orig_pat, int num_files, char_u **files) } } +void cmdline_pum_display(bool changed_array) +{ + pum_display(compl_match_array, compl_match_arraysize, compl_selected, + changed_array, compl_startcol); +} + /* * Show all matches for completion on the command line. * Returns EXPAND_NOTHING when the character that triggered expansion should @@ -4102,12 +4127,28 @@ static int showmatches(expand_T *xp, int wildmenu) showtail = cmd_showtail; } - if (ui_has(kUIWildmenu)) { - Array args = ARRAY_DICT_INIT; + bool compl_use_pum = (ui_has(kUICmdline) + ? ui_has(kUIPopupmenu) + : wildmenu && (wop_flags & WOP_PUM)) + || ui_has(kUIWildmenu); + + if (compl_use_pum) { + compl_match_arraysize = num_files; + compl_match_array = xcalloc(compl_match_arraysize, sizeof(pumitem_T)); for (i = 0; i < num_files; i++) { - ADD(args, STRING_OBJ(cstr_to_string((char *)files_found[i]))); + compl_match_array[i].pum_text = L_SHOWFILE(i); } - ui_call_wildmenu_show(args); + ssize_t offset = showtail ? sm_gettail(xp->xp_pattern)-xp->xp_pattern : 0; + if (ui_has(kUICmdline)) { + compl_startcol = ccline.cmdpos - strnlen((char *)xp->xp_pattern+offset, + xp->xp_pattern_len-offset); + } else { + compl_startcol = ccline.cmdspos + - mb_string2cells_len(xp->xp_pattern+offset, + xp->xp_pattern_len-offset); + } + compl_selected = -1; + cmdline_pum_display(true); return EXPAND_OK; } @@ -6069,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 ee775bab4a..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; } @@ -1634,7 +1641,7 @@ int vim_chdirfile(char_u *fname) } /// Change directory to "new_dir". Search 'cdpath' for relative directory names. -int vim_chdir(char_u *new_dir, CdScope scope) +int vim_chdir(char_u *new_dir) { char_u *dir_name = find_directory_in_path(new_dir, STRLEN(new_dir), FNAME_MESS, curbuf->b_ffname); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 08a4b76783..55463bdf30 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) @@ -2762,7 +2782,7 @@ buf_write ( backup_ext = p_bex; if (backup_copy) { - char_u *wp; + char_u *wp; int some_error = false; char_u *dirp; char_u *rootname; @@ -3456,7 +3476,7 @@ restore_backup: // copy the file. if (os_copy((char *)backup, (char *)fname, UV_FS_COPYFILE_FICLONE) == 0) { - end = 1; + end = 1; // success } } else { if (vim_rename(backup, fname) == 0) { @@ -3591,9 +3611,11 @@ restore_backup: /* * Remove the backup unless 'backup' option is set */ - if (!p_bk && backup != NULL && os_remove((char *)backup) != 0) + if (!p_bk && backup != NULL + && !write_info.bw_conv_error + && os_remove((char *)backup) != 0) { EMSG(_("E207: Can't delete backup file")); - + } goto nofail; @@ -4812,9 +4834,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; @@ -4825,13 +4847,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; @@ -5509,39 +5532,48 @@ static void au_del_cmd(AutoCmd *ac) au_need_clean = true; } -/* - * Cleanup autocommands and patterns that have been deleted. - * This is only done when not executing autocommands. - */ +/// Cleanup autocommands and patterns that have been deleted. +/// This is only done when not executing autocommands. static void au_cleanup(void) { AutoPat *ap, **prev_ap; AutoCmd *ac, **prev_ac; event_T event; - if (autocmd_busy || !au_need_clean) + if (autocmd_busy || !au_need_clean) { return; + } - /* loop over all events */ + // Loop over all events. for (event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) { - /* loop over all autocommand patterns */ + // Loop over all autocommand patterns. prev_ap = &(first_autopat[(int)event]); for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { - /* loop over all commands for this pattern */ + // Loop over all commands for this pattern. prev_ac = &(ap->cmds); + bool has_cmd = false; + for (ac = *prev_ac; ac != NULL; ac = *prev_ac) { - /* remove the command if the pattern is to be deleted or when - * the command has been marked for deletion */ + // Remove the command if the pattern is to be deleted or when + // the command has been marked for deletion. if (ap->pat == NULL || ac->cmd == NULL) { *prev_ac = ac->next; xfree(ac->cmd); xfree(ac); - } else + } else { + has_cmd = true; prev_ac = &(ac->next); + } + } + + if (ap->pat != NULL && !has_cmd) { + // Pattern was not marked for deletion, but all of its commands were. + // So mark the pattern for deletion. + au_remove_pat(ap); } - /* remove the pattern if it has been marked for deletion */ + // Remove the pattern if it has been marked for deletion. if (ap->pat == NULL) { if (ap->next == NULL) { if (prev_ap == &(first_autopat[(int)event])) { @@ -5555,12 +5587,13 @@ static void au_cleanup(void) *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); - } else + } else { prev_ap = &(ap->next); + } } } - au_need_clean = FALSE; + au_need_clean = false; } /* @@ -5965,20 +5998,36 @@ void do_autocmd(char_u *arg_in, int forceit) } } - // Check for "once" flag. cmd = skipwhite(cmd); - if (*cmd != NUL && STRNCMP(cmd, "once", 4) == 0 - && ascii_iswhite(cmd[4])) { - once = true; - cmd = skipwhite(cmd + 4); - } + for (size_t i = 0; i < 2; i++) { + if (*cmd != NUL) { + // Check for "++once" flag. + if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) { + if (once) { + EMSG2(_(e_duparg2), "++once"); + } + once = true; + cmd = skipwhite(cmd + 6); + } - // Check for "nested" flag. - cmd = skipwhite(cmd); - if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 - && ascii_iswhite(cmd[6])) { - nested = true; - cmd = skipwhite(cmd + 6); + // Check for "++nested" flag. + if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) { + if (nested) { + EMSG2(_(e_duparg2), "++nested"); + } + nested = true; + cmd = skipwhite(cmd + 8); + } + + // Check for the old (deprecated) "nested" flag. + if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) { + if (nested) { + EMSG2(_(e_duparg2), "nested"); + } + nested = true; + cmd = skipwhite(cmd + 6); + } + } } // Find the start of the commands. @@ -6393,21 +6442,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 */ @@ -6422,9 +6467,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 @@ -6432,6 +6478,7 @@ aucmd_prepbuf ( win = curwin; aco->save_curwin = curwin; + aco->save_prevwin = prevwin; aco->save_curbuf = curbuf; if (win != NULL) { /* There is a window for "buf" in the current tab page, make it the @@ -6458,21 +6505,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; @@ -6488,8 +6530,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. @@ -6508,9 +6548,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 @@ -6519,8 +6564,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)) { @@ -6529,6 +6572,8 @@ win_found: // Hmm, original window disappeared. Just use the first one. curwin = firstwin; } + prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin + : firstwin; // window disappeared? vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab curbuf = curwin->w_buffer; @@ -6561,6 +6606,8 @@ win_found: } curwin = aco->save_curwin; + prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin + : firstwin; // window disappeared? curbuf = curwin->w_buffer; // In case the autocommand moves the cursor to a position that does not // exist in curbuf @@ -6956,6 +7003,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/fileio.h b/src/nvim/fileio.h index 8db4b89806..a6011ec414 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -23,6 +23,7 @@ typedef struct { buf_T *save_curbuf; ///< saved curbuf int use_aucmd_win; ///< using aucmd_win win_T *save_curwin; ///< saved curwin + win_T *save_prevwin; ///< saved prevwin win_T *new_curwin; ///< new curwin bufref_T new_curbuf; ///< new curbuf char_u *globaldir; ///< saved value of globaldir diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index 6e5e47c060..d3b600a40c 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -205,10 +205,15 @@ #endif #ifdef DEFINE_FUNC_ATTRIBUTES +/// Non-deferred API function. # define FUNC_API_ASYNC +/// Internal C function not exposed in the RPC API. # define FUNC_API_NOEXPORT +/// API function not exposed in VimL/eval. # define FUNC_API_REMOTE_ONLY +/// API function introduced at the given API level. # define FUNC_API_SINCE(X) +/// API function deprecated since the given API level. # define FUNC_API_DEPRECATED_SINCE(X) # define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC # define FUNC_ATTR_ALLOC_SIZE(x) REAL_FATTR_ALLOC_SIZE(x) diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index cb566d46ca..7859d7c71a 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -28,34 +28,41 @@ local lastchar = nil local i local cmd local first = true -local prevfirstchar = nil local byte_a = string.byte('a') local byte_z = string.byte('z') +local a_to_z = byte_z - byte_a + 1 -local cmdidxs = string.format([[ -static const cmdidx_T cmdidxs[%u] = { -]], byte_z - byte_a + 2) +-- Table giving the index of the first command in cmdnames[] to lookup +-- based on the first letter of a command. +local cmdidxs1_out = string.format([[ +static const uint16_t cmdidxs1[%u] = { +]], a_to_z) +-- Table giving the index of the first command in cmdnames[] to lookup +-- based on the first 2 letters of a command. +-- Values in cmdidxs2[c1][c2] are relative to cmdidxs1[c1] so that they +-- fit in a byte. +local cmdidxs2_out = string.format([[ +static const char_u cmdidxs2[%u][%u] = { +/* a b c d e f g h i j k l m n o p q r s t u v w x y z */ + +]], a_to_z, a_to_z) enumfile:write([[ typedef enum CMD_index { ]]) defsfile:write(string.format([[ +static const int command_count = %u; +]], #defs)) +defsfile:write(string.format([[ static CommandDefinition cmdnames[%u] = { ]], #defs)) +local cmds, cmdidxs1, cmdidxs2 = {}, {}, {} for i, cmd in ipairs(defs) do local enumname = cmd.enum or ('CMD_' .. cmd.command) - firstchar = string.byte(cmd.command) - if firstchar ~= prevfirstchar then - if (not prevfirstchar - or (byte_a <= firstchar and firstchar <= byte_z) - or (byte_a <= prevfirstchar and prevfirstchar <= byte_z)) then - if not first then - cmdidxs = cmdidxs .. ',\n' - end - cmdidxs = cmdidxs .. ' ' .. enumname - end - prevfirstchar = firstchar + local byte_cmd = cmd.command:sub(1, 1):byte() + if byte_a <= byte_cmd and byte_cmd <= byte_z then + table.insert(cmds, cmd.command) end if first then first = false @@ -71,6 +78,35 @@ for i, cmd in ipairs(defs) do .cmd_addr_type = %i }]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type)) end +for i = #cmds, 1, -1 do + local cmd = cmds[i] + -- First and second characters of the command + local c1 = cmd:sub(1, 1) + cmdidxs1[c1] = i - 1 + if cmd:len() >= 2 then + local c2 = cmd:sub(2, 2) + local byte_c2 = string.byte(c2) + if byte_a <= byte_c2 and byte_c2 <= byte_z then + if not cmdidxs2[c1] then + cmdidxs2[c1] = {} + end + cmdidxs2[c1][c2] = i - 1 + end + end +end +for i = byte_a, byte_z do + local c1 = string.char(i) + cmdidxs1_out = cmdidxs1_out .. ' /* ' .. c1 .. ' */ ' .. cmdidxs1[c1] .. ',\n' + cmdidxs2_out = cmdidxs2_out .. ' /* ' .. c1 .. ' */ {' + for j = byte_a, byte_z do + local c2 = string.char(j) + cmdidxs2_out = cmdidxs2_out .. + ((cmdidxs2[c1] and cmdidxs2[c1][c2]) + and string.format('%3d', cmdidxs2[c1][c2] - cmdidxs1[c1]) + or ' 0') .. ',' + end + cmdidxs2_out = cmdidxs2_out .. ' },\n' +end defsfile:write([[ }; @@ -81,8 +117,5 @@ enumfile:write([[ CMD_USER_BUF = -2 } cmdidx_T; ]]) -cmdidxs = cmdidxs .. [[ - -}; -]] -defsfile:write(cmdidxs) +defsfile:write(cmdidxs1_out .. '};\n') +defsfile:write(cmdidxs2_out .. '};\n') diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua index 66430ba26e..3130cecd82 100644 --- a/src/nvim/generators/gen_unicode_tables.lua +++ b/src/nvim/generators/gen_unicode_tables.lua @@ -17,7 +17,7 @@ -- which don't have ambiguous or double width, and emoji_all has all Emojis. if arg[1] == '--help' then print('Usage:') - print(' genunicodetables.lua unicode/ unicode_tables.generated.h') + print(' gen_unicode_tables.lua unicode/ unicode_tables.generated.h') os.exit(0) end diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 139bf7b802..ec14803a2e 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1287,9 +1287,9 @@ openscript ( oldcurscript = curscript; do { - update_topline_cursor(); /* update cursor position and topline */ - normal_cmd(&oa, FALSE); /* execute one command */ - vpeekc(); /* check for end of file */ + update_topline_cursor(); // update cursor position and topline + normal_cmd(&oa, false); // execute one command + vpeekc(); // check for end of file } while (scriptin[oldcurscript] != NULL); State = save_State; @@ -1425,49 +1425,54 @@ int vgetc(void) /* a keypad or special function key was not mapped, use it like * its ASCII equivalent */ switch (c) { - case K_KPLUS: c = '+'; break; - case K_KMINUS: c = '-'; break; - case K_KDIVIDE: c = '/'; break; - case K_KMULTIPLY: c = '*'; break; - case K_KENTER: c = CAR; break; - case K_KPOINT: - c = '.'; break; - case K_K0: c = '0'; break; - case K_K1: c = '1'; break; - case K_K2: c = '2'; break; - case K_K3: c = '3'; break; - case K_K4: c = '4'; break; - case K_K5: c = '5'; break; - case K_K6: c = '6'; break; - case K_K7: c = '7'; break; - case K_K8: c = '8'; break; - case K_K9: c = '9'; break; - - case K_XHOME: - case K_ZHOME: if (mod_mask == MOD_MASK_SHIFT) { - c = K_S_HOME; - mod_mask = 0; - } else if (mod_mask == MOD_MASK_CTRL) { - c = K_C_HOME; - mod_mask = 0; - } else - c = K_HOME; - break; - case K_XEND: - case K_ZEND: if (mod_mask == MOD_MASK_SHIFT) { - c = K_S_END; - mod_mask = 0; - } else if (mod_mask == MOD_MASK_CTRL) { - c = K_C_END; - mod_mask = 0; - } else - c = K_END; - break; + case K_KPLUS: c = '+'; break; + case K_KMINUS: c = '-'; break; + case K_KDIVIDE: c = '/'; break; + case K_KMULTIPLY: c = '*'; break; + case K_KENTER: c = CAR; break; + case K_KPOINT: c = '.'; break; + case K_KCOMMA: c = ','; break; + case K_KEQUAL: c = '='; break; + case K_K0: c = '0'; break; + case K_K1: c = '1'; break; + case K_K2: c = '2'; break; + case K_K3: c = '3'; break; + case K_K4: c = '4'; break; + case K_K5: c = '5'; break; + case K_K6: c = '6'; break; + case K_K7: c = '7'; break; + case K_K8: c = '8'; break; + case K_K9: c = '9'; break; + + case K_XHOME: + case K_ZHOME: + if (mod_mask == MOD_MASK_SHIFT) { + c = K_S_HOME; + mod_mask = 0; + } else if (mod_mask == MOD_MASK_CTRL) { + c = K_C_HOME; + mod_mask = 0; + } else { + c = K_HOME; + } + break; + case K_XEND: + case K_ZEND: + if (mod_mask == MOD_MASK_SHIFT) { + c = K_S_END; + mod_mask = 0; + } else if (mod_mask == MOD_MASK_CTRL) { + c = K_C_END; + mod_mask = 0; + } else { + c = K_END; + } + break; - case K_XUP: c = K_UP; break; - case K_XDOWN: c = K_DOWN; break; - case K_XLEFT: c = K_LEFT; break; - case K_XRIGHT: c = K_RIGHT; break; + case K_XUP: c = K_UP; break; + case K_XDOWN: c = K_DOWN; break; + case K_XLEFT: c = K_LEFT; break; + case K_XRIGHT: c = K_RIGHT; break; } /* For a multi-byte character get all the bytes and return the @@ -2265,18 +2270,25 @@ static int vgetorpeek(int advance) // that has a <Nop> RHS. timedout = false; } + + long wait_time = 0; + + if (advance) { + if (typebuf.tb_len == 0 + || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) { + // blocking wait + wait_time = -1L; + } else if (keylen == KEYLEN_PART_KEY && p_ttm >= 0) { + wait_time = p_ttm; + } else { + wait_time = p_tm; + } + } + wait_tb_len = typebuf.tb_len; c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, - typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, - !advance - ? 0 - : ((typebuf.tb_len == 0 - || !(p_timeout || (p_ttimeout - && keylen == KEYLEN_PART_KEY))) - ? -1L - : ((keylen == KEYLEN_PART_KEY && p_ttm >= 0) - ? p_ttm - : p_tm))); + typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, + wait_time); if (i != 0) pop_showcmd(); @@ -2490,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 (*p != NUL) { - *p++ = NUL; + + 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 (args->script) { + noremap = REMAP_SCRIPT; + } + + validate_maphash(); - p = skipwhite(p); - rhs = p; - hasarg = (*rhs != NUL); - haskey = (*keys != NUL); + 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; } @@ -2777,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; } } } @@ -2808,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 } } } @@ -2913,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; } @@ -2927,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 { @@ -2978,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/gettext.h b/src/nvim/gettext.h index acc7e3a92c..629301e8fe 100644 --- a/src/nvim/gettext.h +++ b/src/nvim/gettext.h @@ -11,6 +11,11 @@ # define N_(x) x # endif # define NGETTEXT(x, xs, n) ngettext(x, xs, n) +// On a Mac, gettext's libintl.h defines "setlocale" to be replaced by +// "libintl_setlocal" which leads to wrong return values. #9789 +# if defined(__APPLE__) && defined(setlocale) +# undef setlocale +# endif #else # define _(x) ((char *)(x)) # define N_(x) x diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 004b3252da..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() */ @@ -754,7 +755,6 @@ EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ EXTERN int need_highlight_changed INIT(= true); -EXTERN char *used_shada_file INIT(= NULL); // name of the ShaDa file to use EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. @@ -788,8 +788,6 @@ EXTERN char_u *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd -// for CursorMoved event -EXTERN pos_T last_cursormoved INIT(= { 0, 0, 0 }); EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */ EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */ @@ -933,6 +931,7 @@ EXTERN char_u e_interr[] INIT(= N_("Interrupted")); EXTERN char_u e_invaddr[] INIT(= N_("E14: Invalid address")); EXTERN char_u e_invarg[] INIT(= N_("E474: Invalid argument")); EXTERN char_u e_invarg2[] INIT(= N_("E475: Invalid argument: %s")); +EXTERN char_u e_duparg2[] INIT(= N_("E983: Duplicate argument: %s")); EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s")); EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range")); EXTERN char_u e_invcmd[] INIT(= N_("E476: Invalid command")); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 4c5fca6d39..3ba02be32d 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -160,14 +160,19 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_needs_update = false; // determine window specific background set in 'winhighlight' + bool float_win = wp->w_floating && !wp->w_float_config.external; if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, wp->w_hl_ids[HLF_INACTIVE], true); + } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] > 0) { + wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, + wp->w_hl_ids[HLF_NFLOAT], true); } else if (wp->w_hl_id_normal > 0) { wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true); } else { - wp->w_hl_attr_normal = 0; + wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } + if (wp != curwin) { wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), wp->w_hl_attr_normal); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 1da33bfea5..746d2c2dfc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -90,6 +90,7 @@ typedef enum { , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows , HLF_MSGSEP // message separator line + , HLF_NFLOAT // Floating window , HLF_COUNT // MUST be the last one } hlf_T; @@ -142,6 +143,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_0] = "Whitespace", [HLF_INACTIVE] = "NormalNC", [HLF_MSGSEP] = "MsgSeparator", + [HLF_NFLOAT] = "NormalFloat", }); diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 625d6baa17..8b6fd6c705 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -16,11 +16,10 @@ #include <inttypes.h> #include <fcntl.h> -#include "nvim/vim.h" +#include "nvim/buffer.h" #include "nvim/ascii.h" #include "nvim/if_cscope.h" #include "nvim/charset.h" -#include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/message.h" #include "nvim/memory.h" @@ -29,7 +28,6 @@ #include "nvim/quickfix.h" #include "nvim/strings.h" #include "nvim/tag.h" -#include "nvim/window.h" #include "nvim/os/os.h" #include "nvim/os/input.h" #include "nvim/event/stream.h" diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index ade5487ec8..9145813525 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -163,6 +163,7 @@ static const struct key_name_entry { { K_DEL, "Del" }, { K_DEL, "Delete" }, // Alternative name { K_KDEL, "kDel" }, + { K_KDEL, "KPPeriod" }, // libtermkey name { K_UP, "Up" }, { K_DOWN, "Down" }, { K_LEFT, "Left" }, @@ -171,6 +172,14 @@ static const struct key_name_entry { { K_XDOWN, "xDown" }, { K_XLEFT, "xLeft" }, { K_XRIGHT, "xRight" }, + { K_KUP, "kUp" }, + { K_KUP, "KP8" }, + { K_KDOWN, "kDown" }, + { K_KDOWN, "KP2" }, + { K_KLEFT, "kLeft" }, + { K_KLEFT, "KP4" }, + { K_KRIGHT, "kRight" }, + { K_KRIGHT, "KP6" }, { K_F1, "F1" }, { K_F2, "F2" }, @@ -223,25 +232,41 @@ static const struct key_name_entry { { K_INS, "Insert" }, { K_INS, "Ins" }, // Alternative name { K_KINS, "kInsert" }, + { K_KINS, "KP0" }, { K_HOME, "Home" }, { K_KHOME, "kHome" }, + { K_KHOME, "KP7" }, { K_XHOME, "xHome" }, { K_ZHOME, "zHome" }, { K_END, "End" }, { K_KEND, "kEnd" }, + { K_KEND, "KP1" }, { K_XEND, "xEnd" }, { K_ZEND, "zEnd" }, { K_PAGEUP, "PageUp" }, { K_PAGEDOWN, "PageDown" }, { K_KPAGEUP, "kPageUp" }, + { K_KPAGEUP, "KP9" }, { K_KPAGEDOWN, "kPageDown" }, + { K_KPAGEDOWN, "KP3" }, + { K_KORIGIN, "kOrigin" }, + { K_KORIGIN, "KP5" }, { K_KPLUS, "kPlus" }, + { K_KPLUS, "KPPlus" }, { K_KMINUS, "kMinus" }, + { K_KMINUS, "KPMinus" }, { K_KDIVIDE, "kDivide" }, + { K_KDIVIDE, "KPDiv" }, { K_KMULTIPLY, "kMultiply" }, + { K_KMULTIPLY, "KPMult" }, { K_KENTER, "kEnter" }, + { K_KENTER, "KPEnter" }, { K_KPOINT, "kPoint" }, + { K_KCOMMA, "kComma" }, + { K_KCOMMA, "KPComma" }, + { K_KEQUAL, "kEqual" }, + { K_KEQUAL, "KPEquals" }, { K_K0, "k0" }, { K_K1, "k1" }, @@ -663,7 +688,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, *modp = modifiers; *srcp = end_of_name; return key; - } + } // else { ELOG("unknown key: '%s'", src); } } } return 0; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index baf8963aa8..7f0483826d 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -256,9 +256,13 @@ enum key_extra { #define K_ZERO TERMCAP2KEY(KS_ZERO, KE_FILLER) #define K_UP TERMCAP2KEY('k', 'u') +#define K_KUP TERMCAP2KEY('K', 'u') // keypad up #define K_DOWN TERMCAP2KEY('k', 'd') +#define K_KDOWN TERMCAP2KEY('K', 'd') // keypad down #define K_LEFT TERMCAP2KEY('k', 'l') +#define K_KLEFT TERMCAP2KEY('K', 'l') // keypad left #define K_RIGHT TERMCAP2KEY('k', 'r') +#define K_KRIGHT TERMCAP2KEY('K', 'r') // keypad right #define K_S_UP TERMCAP2KEY(KS_EXTRA, KE_S_UP) #define K_S_DOWN TERMCAP2KEY(KS_EXTRA, KE_S_DOWN) #define K_S_LEFT TERMCAP2KEY('#', '4') @@ -284,7 +288,7 @@ enum key_extra { #define K_XLEFT TERMCAP2KEY(KS_EXTRA, KE_XLEFT) #define K_XRIGHT TERMCAP2KEY(KS_EXTRA, KE_XRIGHT) -#define K_F1 TERMCAP2KEY('k', '1') /* function keys */ +#define K_F1 TERMCAP2KEY('k', '1') // function keys #define K_F2 TERMCAP2KEY('k', '2') #define K_F3 TERMCAP2KEY('k', '3') #define K_F4 TERMCAP2KEY('k', '4') @@ -331,7 +335,7 @@ enum key_extra { #define K_S_XF3 TERMCAP2KEY(KS_EXTRA, KE_S_XF3) #define K_S_XF4 TERMCAP2KEY(KS_EXTRA, KE_S_XF4) -#define K_S_F1 TERMCAP2KEY(KS_EXTRA, KE_S_F1) /* shifted func. keys */ +#define K_S_F1 TERMCAP2KEY(KS_EXTRA, KE_S_F1) // shifted func. keys #define K_S_F2 TERMCAP2KEY(KS_EXTRA, KE_S_F2) #define K_S_F3 TERMCAP2KEY(KS_EXTRA, KE_S_F3) #define K_S_F4 TERMCAP2KEY(KS_EXTRA, KE_S_F4) @@ -356,35 +360,39 @@ enum key_extra { #define K_DEL TERMCAP2KEY('k', 'D') #define K_KDEL TERMCAP2KEY(KS_EXTRA, KE_KDEL) #define K_HOME TERMCAP2KEY('k', 'h') -#define K_KHOME TERMCAP2KEY('K', '1') /* keypad home (upper left) */ +#define K_KHOME TERMCAP2KEY('K', '1') // keypad home (upper left) #define K_XHOME TERMCAP2KEY(KS_EXTRA, KE_XHOME) #define K_ZHOME TERMCAP2KEY(KS_EXTRA, KE_ZHOME) #define K_END TERMCAP2KEY('@', '7') -#define K_KEND TERMCAP2KEY('K', '4') /* keypad end (lower left) */ +#define K_KEND TERMCAP2KEY('K', '4') // keypad end (lower left) #define K_XEND TERMCAP2KEY(KS_EXTRA, KE_XEND) #define K_ZEND TERMCAP2KEY(KS_EXTRA, KE_ZEND) #define K_PAGEUP TERMCAP2KEY('k', 'P') #define K_PAGEDOWN TERMCAP2KEY('k', 'N') -#define K_KPAGEUP TERMCAP2KEY('K', '3') /* keypad pageup (upper R.) */ -#define K_KPAGEDOWN TERMCAP2KEY('K', '5') /* keypad pagedown (lower R.) */ - -#define K_KPLUS TERMCAP2KEY('K', '6') /* keypad plus */ -#define K_KMINUS TERMCAP2KEY('K', '7') /* keypad minus */ -#define K_KDIVIDE TERMCAP2KEY('K', '8') /* keypad / */ -#define K_KMULTIPLY TERMCAP2KEY('K', '9') /* keypad * */ -#define K_KENTER TERMCAP2KEY('K', 'A') /* keypad Enter */ -#define K_KPOINT TERMCAP2KEY('K', 'B') /* keypad . or ,*/ - -#define K_K0 TERMCAP2KEY('K', 'C') /* keypad 0 */ -#define K_K1 TERMCAP2KEY('K', 'D') /* keypad 1 */ -#define K_K2 TERMCAP2KEY('K', 'E') /* keypad 2 */ -#define K_K3 TERMCAP2KEY('K', 'F') /* keypad 3 */ -#define K_K4 TERMCAP2KEY('K', 'G') /* keypad 4 */ -#define K_K5 TERMCAP2KEY('K', 'H') /* keypad 5 */ -#define K_K6 TERMCAP2KEY('K', 'I') /* keypad 6 */ -#define K_K7 TERMCAP2KEY('K', 'J') /* keypad 7 */ -#define K_K8 TERMCAP2KEY('K', 'K') /* keypad 8 */ -#define K_K9 TERMCAP2KEY('K', 'L') /* keypad 9 */ +#define K_KPAGEUP TERMCAP2KEY('K', '3') // keypad pageup (upper R.) +#define K_KPAGEDOWN TERMCAP2KEY('K', '5') // keypad pagedown (lower R.) +#define K_KORIGIN TERMCAP2KEY('K', '2') // keypad center + +#define K_KPLUS TERMCAP2KEY('K', '6') // keypad plus +#define K_KMINUS TERMCAP2KEY('K', '7') // keypad minus +#define K_KDIVIDE TERMCAP2KEY('K', '8') // keypad / +#define K_KMULTIPLY TERMCAP2KEY('K', '9') // keypad * +#define K_KENTER TERMCAP2KEY('K', 'A') // keypad Enter +#define K_KPOINT TERMCAP2KEY('K', 'B') // keypad . or , + +#define K_K0 TERMCAP2KEY('K', 'C') // keypad 0 +#define K_K1 TERMCAP2KEY('K', 'D') // keypad 1 +#define K_K2 TERMCAP2KEY('K', 'E') // keypad 2 +#define K_K3 TERMCAP2KEY('K', 'F') // keypad 3 +#define K_K4 TERMCAP2KEY('K', 'G') // keypad 4 +#define K_K5 TERMCAP2KEY('K', 'H') // keypad 5 +#define K_K6 TERMCAP2KEY('K', 'I') // keypad 6 +#define K_K7 TERMCAP2KEY('K', 'J') // keypad 7 +#define K_K8 TERMCAP2KEY('K', 'K') // keypad 8 +#define K_K9 TERMCAP2KEY('K', 'L') // keypad 9 + +#define K_KCOMMA TERMCAP2KEY('K', 'M') // keypad comma +#define K_KEQUAL TERMCAP2KEY('K', 'N') // keypad equal #define K_MOUSE TERMCAP2KEY(KS_MOUSE, KE_FILLER) #define K_MENU TERMCAP2KEY(KS_MENU, KE_FILLER) diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h index e2688064a8..704aa26010 100644 --- a/src/nvim/lib/kbtree.h +++ b/src/nvim/lib/kbtree.h @@ -31,6 +31,7 @@ #include <stdlib.h> #include <string.h> #include <stdint.h> +#include <assert.h> #include "nvim/memory.h" @@ -317,7 +318,7 @@ #define __KB_ITR(name, key_t, kbnode_t) \ static inline void kb_itr_first_##name(kbtree_##name##_t *b, kbitr_##name##_t *itr) \ { \ - itr->p = 0; \ + itr->p = NULL; \ if (b->n_keys == 0) return; \ itr->p = itr->stack; \ itr->p->x = b->root; itr->p->i = 0; \ @@ -329,30 +330,37 @@ } \ static inline int kb_itr_next_##name(kbtree_##name##_t *b, kbitr_##name##_t *itr) \ { \ - if (itr->p < itr->stack) return 0; \ + if (itr->p == NULL) return 0; \ for (;;) { \ ++itr->p->i; \ + assert(itr->p->i <= 21); \ while (itr->p->x && itr->p->i <= itr->p->x->n) { \ itr->p[1].i = 0; \ itr->p[1].x = itr->p->x->is_internal? __KB_PTR(b, itr->p->x)[itr->p->i] : 0; \ ++itr->p; \ } \ + if (itr->p == itr->stack) { \ + itr->p = NULL; \ + return 0; \ + } \ --itr->p; \ - if (itr->p < itr->stack) return 0; \ if (itr->p->x && itr->p->i < itr->p->x->n) return 1; \ } \ } \ static inline int kb_itr_prev_##name(kbtree_##name##_t *b, kbitr_##name##_t *itr) \ { \ - if (itr->p < itr->stack) return 0; \ + if (itr->p == NULL) return 0; \ for (;;) { \ while (itr->p->x && itr->p->i >= 0) { \ itr->p[1].x = itr->p->x->is_internal? __KB_PTR(b, itr->p->x)[itr->p->i] : 0; \ itr->p[1].i = itr->p[1].x ? itr->p[1].x->n : -1; \ ++itr->p; \ } \ + if (itr->p == itr->stack) { \ + itr->p = NULL; \ + return 0; \ + } \ --itr->p; \ - if (itr->p < itr->stack) return 0; \ --itr->p->i; \ if (itr->p->x && itr->p->i >= 0) return 1; \ } \ @@ -371,9 +379,11 @@ itr->p->i = i; \ if (i >= 0 && r == 0) return 1; \ ++itr->p->i; \ + assert(itr->p->i <= 21); \ itr->p[1].x = itr->p->x->is_internal? __KB_PTR(b, itr->p->x)[i + 1] : 0; \ ++itr->p; \ } \ + itr->p->i = 0; \ return 0; \ } \ static inline int kb_itr_get_##name(kbtree_##name##_t *b, key_t k, kbitr_##name##_t *itr) \ diff --git a/src/nvim/log.c b/src/nvim/log.c index 8066b6e828..a2f83d4d09 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -1,6 +1,13 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// +// Log module +// +// How Linux printk() handles recursion, buffering, etc: +// https://lwn.net/Articles/780556/ +// + #include <assert.h> #include <inttypes.h> #include <stdarg.h> @@ -99,9 +106,14 @@ void log_unlock(void) uv_mutex_unlock(&mutex); } -/// @param context description of a shared context or subsystem -/// @param func_name function name, or NULL -/// @param line_num source line number, or -1 +/// Logs a message to $NVIM_LOG_FILE. +/// +/// @param log_level Log level (see log.h) +/// @param context Description of a shared context or subsystem +/// @param func_name Function name, or NULL +/// @param line_num Source line number, or -1 +/// @param eol Append linefeed "\n" +/// @param fmt printf-style format string bool logmsg(int log_level, const char *context, const char *func_name, int line_num, bool eol, const char *fmt, ...) FUNC_ATTR_UNUSED FUNC_ATTR_PRINTF(6, 7) @@ -163,7 +175,8 @@ end: FILE *open_log_file(void) { static bool opening_log_file = false; - // check if it's a recursive call + // Disallow recursion. (This only matters for log_path_init; for logmsg and + // friends we use a mutex: log_lock). if (opening_log_file) { do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true, "Cannot LOG() recursively."); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 93069893cf..72b97736fc 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -24,6 +24,10 @@ #include "nvim/undo.h" #include "nvim/ascii.h" +#ifdef WIN32 +#include "nvim/os/os.h" +#endif + #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" @@ -118,6 +122,14 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "debug"); lua_pop(lstate, 1); +#ifdef WIN32 + // os.getenv + lua_getglobal(lstate, "os"); + lua_pushcfunction(lstate, &nlua_getenv); + lua_setfield(lstate, -2, "getenv"); + lua_pop(lstate, 1); +#endif + // vim if (luaL_dostring(lstate, (char *)&vim_module[0])) { nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); @@ -297,7 +309,7 @@ nlua_print_error: return 0; } -/// debug.debug implementation: interaction with user while debugging +/// debug.debug: interaction with user while debugging. /// /// @param lstate Lua interpreter state. int nlua_debug(lua_State *lstate) @@ -337,6 +349,19 @@ int nlua_debug(lua_State *lstate) return 0; } +#ifdef WIN32 +/// os.getenv: override os.getenv to maintain coherency. #9681 +/// +/// uv_os_setenv uses SetEnvironmentVariableW which does not update _environ. +/// +/// @param lstate Lua interpreter state. +static int nlua_getenv(lua_State *lstate) +{ + lua_pushstring(lstate, os_getenv(luaL_checkstring(lstate, 1))); + return 1; +} +#endif + /// Evaluate lua string /// /// Used for luaeval(). diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b0d0bfc74b..9cd8e232d5 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -1,3 +1,39 @@ +-- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib) +-- +-- Lua code lives in one of three places: +-- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the +-- `inspect` and `lpeg` modules. +-- 2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests. +-- 3. src/nvim/lua/: Compiled-into Nvim itself. +-- +-- Guideline: "If in doubt, put it in the runtime". +-- +-- Most functions should live directly on `vim.`, not sub-modules. The only +-- "forbidden" names are those claimed by legacy `if_lua`: +-- $ vim +-- :lua for k,v in pairs(vim) do print(k) end +-- buffer +-- open +-- window +-- lastline +-- firstline +-- type +-- line +-- eval +-- dict +-- beep +-- list +-- command +-- +-- Reference (#6580): +-- - https://github.com/luafun/luafun +-- - https://github.com/rxi/lume +-- - http://leafo.net/lapis/reference/utilities.html +-- - https://github.com/torch/paths +-- - https://github.com/bakpakin/Fennel (pretty print, repl) +-- - https://github.com/howl-editor/howl/tree/master/lib/howl/util + + -- Internal-only until comments in #8107 are addressed. -- Returns: -- {errcode}, {output} @@ -118,79 +154,24 @@ local function _update_package_paths() last_nvim_paths = cur_nvim_paths end -local function gsplit(s, sep, plain) - assert(type(s) == "string") - assert(type(sep) == "string") - assert(type(plain) == "boolean" or type(plain) == "nil") - - local start = 1 - local done = false - - local function pass(i, j, ...) - if i then - assert(j+1 > start, "Infinite loop detected") - local seg = s:sub(start, i - 1) - start = j + 1 - return seg, ... - else - done = true - return s:sub(start) - end - end - - return function() - if done then - return - end - if sep == '' then - if start == #s then - done = true - end - return pass(start+1, start) - end - return pass(s:find(sep, start, plain)) - end -end - -local function split(s,sep,plain) - local t={} for c in gsplit(s, sep, plain) do table.insert(t,c) end - return t -end - +--- Trim whitespace (Lua pattern "%%s") from both sides of a string. +--- +--@see https://www.lua.org/pil/20.2.html +--@param s String to trim +--@returns String with whitespace removed from its beginning and end local function trim(s) - assert(type(s) == "string", "Only strings can be trimmed") - local result = s:gsub("^%s+", ""):gsub("%s+$", "") - return result -end - -local deepcopy - -local function id(v) - return v -end - -local deepcopy_funcs = { - table = function(orig) - local copy = {} - for k, v in pairs(orig) do - copy[deepcopy(k)] = deepcopy(v) - end - return copy - end, - number = id, - string = id, - ['nil'] = id, - boolean = id, -} - -deepcopy = function(orig) - return deepcopy_funcs[type(orig)](orig) + assert(type(s) == 'string', 'Only strings can be trimmed') + return s:match('^%s*(.*%S)') or '' end -local function __index(table, key) - if key == "inspect" then - table.inspect = require("vim.inspect") - return table.inspect +local function __index(t, key) + if key == 'inspect' then + t.inspect = require('vim.inspect') + return t.inspect + elseif require('vim.shared')[key] ~= nil then + -- Expose all `vim.shared` functions on the `vim` module. + t[key] = require('vim.shared')[key] + return t[key] end end @@ -200,9 +181,6 @@ local module = { _os_proc_info = _os_proc_info, _system = _system, trim = trim, - split = split, - gsplit = gsplit, - deepcopy = deepcopy, } setmetatable(module, { diff --git a/src/nvim/main.c b/src/nvim/main.c index 5dffca95a2..4e1c7dff57 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -114,12 +114,12 @@ typedef struct { char *listen_addr; // --listen {address} } mparm_T; -/* Values for edit_type. */ -#define EDIT_NONE 0 /* no edit type yet */ -#define EDIT_FILE 1 /* file name argument[s] given, use argument list */ -#define EDIT_STDIN 2 /* read file from stdin */ -#define EDIT_TAG 3 /* tag name argument given, use tagname */ -#define EDIT_QF 4 /* start in quickfix mode */ +// Values for edit_type. +#define EDIT_NONE 0 // no edit type yet +#define EDIT_FILE 1 // file name argument[s] given, use argument list +#define EDIT_STDIN 2 // read file from stdin +#define EDIT_TAG 3 // tag name argument given, use tagname +#define EDIT_QF 4 // start in quickfix mode #ifdef INCLUDE_GENERATED_DECLARATIONS # include "main.c.generated.h" @@ -547,10 +547,11 @@ int main(int argc, char **argv) if (params.n_commands > 0) exe_commands(¶ms); + starting = 0; + RedrawingDisabled = 0; redraw_all_later(NOT_VALID); - no_wait_return = FALSE; - starting = 0; + no_wait_return = false; // 'autochdir' has been postponed. do_autochdir(); @@ -806,6 +807,7 @@ static void command_line_scan(mparm_T *parmp) if (exmode_active) { // "nvim -e -" silent mode silent_mode = true; + parmp->no_swap_file = true; } else { if (parmp->edit_type != EDIT_NONE && parmp->edit_type != EDIT_FILE @@ -869,6 +871,9 @@ static void command_line_scan(mparm_T *parmp) } else if (STRNICMP(argv[0] + argv_idx, "startuptime", 11) == 0) { want_argument = true; argv_idx += 11; + } else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) { + parmp->use_vimrc = "NONE"; + set_option_value("shadafile", 0L, "NONE", 0); } else { if (argv[0][argv_idx]) mainerr(err_opt_unknown, argv[0]); @@ -989,6 +994,7 @@ static void command_line_scan(mparm_T *parmp) case 's': { if (exmode_active) { // "-es" silent (batch) Ex-mode silent_mode = true; + parmp->no_swap_file = true; } else { // "-s {scriptin}" read from script file want_argument = true; } @@ -1126,7 +1132,7 @@ static void command_line_scan(mparm_T *parmp) } case 'i': { // "-i {shada}" use for shada - used_shada_file = argv[0]; + set_option_value("shadafile", 0L, argv[0], 0); break; } @@ -1514,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/map.c b/src/nvim/map.c index 9b6f57a56f..90da27cf9f 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -1,12 +1,12 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/// -/// map.c: khash.h wrapper -/// -/// NOTE: Callers must manage memory (allocate) for keys and values. -/// khash.h does not make its own copy of the key or value. -/// +// +// map.c: khash.h wrapper +// +// NOTE: Callers must manage memory (allocate) for keys and values. +// khash.h does not make its own copy of the key or value. +// #include <stdlib.h> #include <stdbool.h> diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 602648c27c..5c9367ab01 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -214,7 +214,7 @@ pos_T *movemark(int count) pos_T *pos; xfmark_T *jmp; - cleanup_jumplist(); + cleanup_jumplist(curwin, true); if (curwin->w_jumplistlen == 0) /* nothing to jump to */ return (pos_T *)NULL; @@ -781,13 +781,11 @@ void ex_jumps(exarg_T *eap) int i; char_u *name; - cleanup_jumplist(); - /* Highlight title */ + cleanup_jumplist(curwin, true); + // Highlight title MSG_PUTS_TITLE(_("\n jump line col file/text")); for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { - if (curwin->w_jumplist[i].fmark.fnum == 0) - fname2fnum(&curwin->w_jumplist[i]); name = fm_getname(&curwin->w_jumplist[i].fmark, 16); if (name == NULL) /* file name not available */ continue; @@ -1069,19 +1067,24 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, { \ posp->lnum += lnum_amount; \ assert(col_amount > INT_MIN && col_amount <= INT_MAX); \ - if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) \ + if (col_amount < 0 && posp->col <= (colnr_T)-col_amount) { \ posp->col = 0; \ - else \ + } else if (posp->col < spaces_removed) { \ + posp->col = (int)col_amount + spaces_removed; \ + } else { \ posp->col += (colnr_T)col_amount; \ + } \ } \ } -/* - * Adjust marks in line "lnum" at column "mincol" and further: add - * "lnum_amount" to the line number and add "col_amount" to the column - * position. - */ -void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount) +// Adjust marks in line "lnum" at column "mincol" and further: add +// "lnum_amount" to the line number and add "col_amount" to the column +// position. +// "spaces_removed" is the number of spaces that were removed, matters when the +// cursor is inside them. +void mark_col_adjust( + linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount, + int spaces_removed) { int i; int fnum = curbuf->b_fnum; @@ -1151,41 +1154,69 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, long lnum_amount, long col_a } } -/* - * When deleting lines, this may create duplicate marks in the - * jumplist. They will be removed here for the current window. - */ -void cleanup_jumplist(void) +// When deleting lines, this may create duplicate marks in the +// jumplist. They will be removed here for the specified window. +// When "loadfiles" is true first ensure entries have the "fnum" field set +// (this may be a bit slow). +void cleanup_jumplist(win_T *wp, bool loadfiles) { int i; - int from, to; - to = 0; - for (from = 0; from < curwin->w_jumplistlen; ++from) { - if (curwin->w_jumplistidx == from) - curwin->w_jumplistidx = to; - for (i = from + 1; i < curwin->w_jumplistlen; ++i) - if (curwin->w_jumplist[i].fmark.fnum - == curwin->w_jumplist[from].fmark.fnum - && curwin->w_jumplist[from].fmark.fnum != 0 - && curwin->w_jumplist[i].fmark.mark.lnum - == curwin->w_jumplist[from].fmark.mark.lnum) + if (loadfiles) { + // If specified, load all the files from the jump list. This is + // needed to properly clean up duplicate entries, but will take some + // time. + for (i = 0; i < wp->w_jumplistlen; i++) { + if ((wp->w_jumplist[i].fmark.fnum == 0) + && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { + fname2fnum(&wp->w_jumplist[i]); + } + } + } + + int to = 0; + for (int from = 0; from < wp->w_jumplistlen; from++) { + if (wp->w_jumplistidx == from) { + wp->w_jumplistidx = to; + } + for (i = from + 1; i < wp->w_jumplistlen; i++) { + if (wp->w_jumplist[i].fmark.fnum + == wp->w_jumplist[from].fmark.fnum + && wp->w_jumplist[from].fmark.fnum != 0 + && wp->w_jumplist[i].fmark.mark.lnum + == wp->w_jumplist[from].fmark.mark.lnum) { break; - if (i >= curwin->w_jumplistlen) { // no duplicate + } + } + if (i >= wp->w_jumplistlen) { // no duplicate if (to != from) { - // Not using curwin->w_jumplist[to++] = curwin->w_jumplist[from] because + // Not using wp->w_jumplist[to++] = wp->w_jumplist[from] because // this way valgrind complains about overlapping source and destination // in memcpy() call. (clang-3.6.0, debug build with -DEXITFREE). - curwin->w_jumplist[to] = curwin->w_jumplist[from]; + wp->w_jumplist[to] = wp->w_jumplist[from]; } to++; } else { - xfree(curwin->w_jumplist[from].fname); + xfree(wp->w_jumplist[from].fname); + } + } + if (wp->w_jumplistidx == wp->w_jumplistlen) { + wp->w_jumplistidx = to; + } + wp->w_jumplistlen = to; + + // When pointer is below last jump, remove the jump if it matches the current + // line. This avoids useless/phantom jumps. #9805 + if (wp->w_jumplistlen + && wp->w_jumplistidx == wp->w_jumplistlen) { + const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; + if (fm_last->fmark.fnum == curbuf->b_fnum + && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { + xfree(fm_last->fname); + wp->w_jumplistlen--; + wp->w_jumplistidx--; } } - if (curwin->w_jumplistidx == curwin->w_jumplistlen) - curwin->w_jumplistidx = to; - curwin->w_jumplistlen = to; } /* diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 5ed2b4c564..8cc91146cc 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -555,6 +555,24 @@ size_t mb_string2cells(const char_u *str) return clen; } +/// Get the number of cells occupied by string `str` with maximum length `size` +/// +/// @param str The source string, may not be NULL, must be a NUL-terminated +/// string. +/// @param size maximum length of string. It will terminate on earlier NUL. +/// @return The number of cells occupied by string `str` +size_t mb_string2cells_len(const char_u *str, size_t size) +{ + size_t clen = 0; + + for (const char_u *p = str; *p != NUL && p < str+size; + p += utf_ptr2len_len(p, size+(p-str))) { + clen += utf_ptr2cells(p); + } + + return clen; +} + /// Convert a UTF-8 byte sequence to a wide character /// /// If the sequence is illegal or truncated by a NUL then the first byte is @@ -1045,75 +1063,78 @@ int utf_class_tab(const int c, const uint64_t *const chartab) unsigned int first; unsigned int last; unsigned int class; - } classes[] = - { - {0x037e, 0x037e, 1}, /* Greek question mark */ - {0x0387, 0x0387, 1}, /* Greek ano teleia */ - {0x055a, 0x055f, 1}, /* Armenian punctuation */ - {0x0589, 0x0589, 1}, /* Armenian full stop */ - {0x05be, 0x05be, 1}, - {0x05c0, 0x05c0, 1}, - {0x05c3, 0x05c3, 1}, - {0x05f3, 0x05f4, 1}, - {0x060c, 0x060c, 1}, - {0x061b, 0x061b, 1}, - {0x061f, 0x061f, 1}, - {0x066a, 0x066d, 1}, - {0x06d4, 0x06d4, 1}, - {0x0700, 0x070d, 1}, /* Syriac punctuation */ - {0x0964, 0x0965, 1}, - {0x0970, 0x0970, 1}, - {0x0df4, 0x0df4, 1}, - {0x0e4f, 0x0e4f, 1}, - {0x0e5a, 0x0e5b, 1}, - {0x0f04, 0x0f12, 1}, - {0x0f3a, 0x0f3d, 1}, - {0x0f85, 0x0f85, 1}, - {0x104a, 0x104f, 1}, /* Myanmar punctuation */ - {0x10fb, 0x10fb, 1}, /* Georgian punctuation */ - {0x1361, 0x1368, 1}, /* Ethiopic punctuation */ - {0x166d, 0x166e, 1}, /* Canadian Syl. punctuation */ - {0x1680, 0x1680, 0}, - {0x169b, 0x169c, 1}, - {0x16eb, 0x16ed, 1}, - {0x1735, 0x1736, 1}, - {0x17d4, 0x17dc, 1}, /* Khmer punctuation */ - {0x1800, 0x180a, 1}, /* Mongolian punctuation */ - {0x2000, 0x200b, 0}, /* spaces */ - {0x200c, 0x2027, 1}, /* punctuation and symbols */ - {0x2028, 0x2029, 0}, - {0x202a, 0x202e, 1}, /* punctuation and symbols */ - {0x202f, 0x202f, 0}, - {0x2030, 0x205e, 1}, /* punctuation and symbols */ - {0x205f, 0x205f, 0}, - {0x2060, 0x27ff, 1}, /* punctuation and symbols */ - {0x2070, 0x207f, 0x2070}, /* superscript */ - {0x2080, 0x2094, 0x2080}, /* subscript */ - {0x20a0, 0x27ff, 1}, /* all kinds of symbols */ - {0x2800, 0x28ff, 0x2800}, /* braille */ - {0x2900, 0x2998, 1}, /* arrows, brackets, etc. */ - {0x29d8, 0x29db, 1}, - {0x29fc, 0x29fd, 1}, - {0x2e00, 0x2e7f, 1}, /* supplemental punctuation */ - {0x3000, 0x3000, 0}, /* ideographic space */ - {0x3001, 0x3020, 1}, /* ideographic punctuation */ - {0x3030, 0x3030, 1}, - {0x303d, 0x303d, 1}, - {0x3040, 0x309f, 0x3040}, /* Hiragana */ - {0x30a0, 0x30ff, 0x30a0}, /* Katakana */ - {0x3300, 0x9fff, 0x4e00}, /* CJK Ideographs */ - {0xac00, 0xd7a3, 0xac00}, /* Hangul Syllables */ - {0xf900, 0xfaff, 0x4e00}, /* CJK Ideographs */ - {0xfd3e, 0xfd3f, 1}, - {0xfe30, 0xfe6b, 1}, /* punctuation forms */ - {0xff00, 0xff0f, 1}, /* half/fullwidth ASCII */ - {0xff1a, 0xff20, 1}, /* half/fullwidth ASCII */ - {0xff3b, 0xff40, 1}, /* half/fullwidth ASCII */ - {0xff5b, 0xff65, 1}, /* half/fullwidth ASCII */ - {0x20000, 0x2a6df, 0x4e00}, /* CJK Ideographs */ - {0x2a700, 0x2b73f, 0x4e00}, /* CJK Ideographs */ - {0x2b740, 0x2b81f, 0x4e00}, /* CJK Ideographs */ - {0x2f800, 0x2fa1f, 0x4e00}, /* CJK Ideographs */ + } classes[] = { + { 0x037e, 0x037e, 1 }, // Greek question mark + { 0x0387, 0x0387, 1 }, // Greek ano teleia + { 0x055a, 0x055f, 1 }, // Armenian punctuation + { 0x0589, 0x0589, 1 }, // Armenian full stop + { 0x05be, 0x05be, 1 }, + { 0x05c0, 0x05c0, 1 }, + { 0x05c3, 0x05c3, 1 }, + { 0x05f3, 0x05f4, 1 }, + { 0x060c, 0x060c, 1 }, + { 0x061b, 0x061b, 1 }, + { 0x061f, 0x061f, 1 }, + { 0x066a, 0x066d, 1 }, + { 0x06d4, 0x06d4, 1 }, + { 0x0700, 0x070d, 1 }, // Syriac punctuation + { 0x0964, 0x0965, 1 }, + { 0x0970, 0x0970, 1 }, + { 0x0df4, 0x0df4, 1 }, + { 0x0e4f, 0x0e4f, 1 }, + { 0x0e5a, 0x0e5b, 1 }, + { 0x0f04, 0x0f12, 1 }, + { 0x0f3a, 0x0f3d, 1 }, + { 0x0f85, 0x0f85, 1 }, + { 0x104a, 0x104f, 1 }, // Myanmar punctuation + { 0x10fb, 0x10fb, 1 }, // Georgian punctuation + { 0x1361, 0x1368, 1 }, // Ethiopic punctuation + { 0x166d, 0x166e, 1 }, // Canadian Syl. punctuation + { 0x1680, 0x1680, 0 }, + { 0x169b, 0x169c, 1 }, + { 0x16eb, 0x16ed, 1 }, + { 0x1735, 0x1736, 1 }, + { 0x17d4, 0x17dc, 1 }, // Khmer punctuation + { 0x1800, 0x180a, 1 }, // Mongolian punctuation + { 0x2000, 0x200b, 0 }, // spaces + { 0x200c, 0x2027, 1 }, // punctuation and symbols + { 0x2028, 0x2029, 0 }, + { 0x202a, 0x202e, 1 }, // punctuation and symbols + { 0x202f, 0x202f, 0 }, + { 0x2030, 0x205e, 1 }, // punctuation and symbols + { 0x205f, 0x205f, 0 }, + { 0x2060, 0x27ff, 1 }, // punctuation and symbols + { 0x2070, 0x207f, 0x2070 }, // superscript + { 0x2080, 0x2094, 0x2080 }, // subscript + { 0x20a0, 0x27ff, 1 }, // all kinds of symbols + { 0x2800, 0x28ff, 0x2800 }, // braille + { 0x2900, 0x2998, 1 }, // arrows, brackets, etc. + { 0x29d8, 0x29db, 1 }, + { 0x29fc, 0x29fd, 1 }, + { 0x2e00, 0x2e7f, 1 }, // supplemental punctuation + { 0x3000, 0x3000, 0 }, // ideographic space + { 0x3001, 0x3020, 1 }, // ideographic punctuation + { 0x3030, 0x3030, 1 }, + { 0x303d, 0x303d, 1 }, + { 0x3040, 0x309f, 0x3040 }, // Hiragana + { 0x30a0, 0x30ff, 0x30a0 }, // Katakana + { 0x3300, 0x9fff, 0x4e00 }, // CJK Ideographs + { 0xac00, 0xd7a3, 0xac00 }, // Hangul Syllables + { 0xf900, 0xfaff, 0x4e00 }, // CJK Ideographs + { 0xfd3e, 0xfd3f, 1 }, + { 0xfe30, 0xfe6b, 1 }, // punctuation forms + { 0xff00, 0xff0f, 1 }, // half/fullwidth ASCII + { 0xff1a, 0xff20, 1 }, // half/fullwidth ASCII + { 0xff3b, 0xff40, 1 }, // half/fullwidth ASCII + { 0xff5b, 0xff65, 1 }, // half/fullwidth ASCII + { 0x1d000, 0x1d24f, 1 }, // Musical notation + { 0x1d400, 0x1d7ff, 1 }, // Mathematical Alphanumeric Symbols + { 0x1f000, 0x1f2ff, 1 }, // Game pieces; enclosed characters + { 0x1f300, 0x1f9ff, 1 }, // Many symbol blocks + { 0x20000, 0x2a6df, 0x4e00 }, // CJK Ideographs + { 0x2a700, 0x2b73f, 0x4e00 }, // CJK Ideographs + { 0x2b740, 0x2b81f, 0x4e00 }, // CJK Ideographs + { 0x2f800, 0x2fa1f, 0x4e00 }, // CJK Ideographs }; int bot = 0; int top = ARRAY_SIZE(classes) - 1; 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 aea297fce2..472481bb30 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -171,7 +171,7 @@ ex_menu(exarg_T *eap) if (enable != kNone) { // Change sensitivity of the menu. // For the PopUp menu, remove a menu for each mode separately. - // Careful: menu_nable_recurse() changes menu_path. + // Careful: menu_enable_recurse() changes menu_path. if (STRCMP(menu_path, "*") == 0) { // meaning: do all menus menu_path = (char_u *)""; } @@ -180,11 +180,11 @@ ex_menu(exarg_T *eap) for (i = 0; i < MENU_INDEX_TIP; ++i) if (modes & (1 << i)) { p = popup_mode_name(menu_path, i); - menu_nable_recurse(root_menu, p, MENU_ALL_MODES, enable); + menu_enable_recurse(root_menu, p, MENU_ALL_MODES, enable); xfree(p); } } - menu_nable_recurse(root_menu, menu_path, modes, enable); + menu_enable_recurse(root_menu, menu_path, modes, enable); } else if (unmenu) { /* * Delete menu(s). @@ -485,7 +485,10 @@ erret: * Set the (sub)menu with the given name to enabled or disabled. * Called recursively. */ -static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enable) +static int menu_enable_recurse(vimmenu_T *menu, + char_u *name, + int modes, + int enable) { char_u *p; @@ -503,13 +506,14 @@ static int menu_nable_recurse(vimmenu_T *menu, char_u *name, int modes, int enab EMSG(_(e_notsubmenu)); return FAIL; } - if (menu_nable_recurse(menu->children, p, modes, enable) - == FAIL) + if (menu_enable_recurse(menu->children, p, modes, enable) == FAIL) { return FAIL; - } else if (enable) + } + } else if (enable) { menu->enabled |= modes; - else + } else { menu->enabled &= ~modes; + } /* * When name is empty, we are doing all menu items for the given @@ -770,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 b4aa333a48..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; @@ -384,7 +380,7 @@ int smsg(char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist, NULL); + vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); va_end(arglist); return msg(IObuff); } @@ -395,11 +391,22 @@ int smsg_attr(int attr, char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist, NULL); + vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); va_end(arglist); return msg_attr((const char *)IObuff, attr); } +int smsg_attr_keep(int attr, char *s, ...) + FUNC_ATTR_PRINTF(2, 3) +{ + va_list arglist; + + va_start(arglist, s); + vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + va_end(arglist); + return msg_attr_keep(IObuff, attr, true, false); +} + /* * Remember the last sourcing name/lnum used in an error message, so that it * isn't printed each time when it didn't change. @@ -662,7 +669,7 @@ bool emsgf_multiline(const char *const fmt, ...) } va_start(ap, fmt); - vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); + vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); va_end(ap); ret = emsg_multiline(errbuf, true); @@ -678,7 +685,7 @@ static bool emsgfv(const char *fmt, va_list ap) return true; } - vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); + vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap); return emsg((const char_u *)errbuf); } @@ -726,7 +733,7 @@ void msg_schedule_emsgf(const char *const fmt, ...) { va_list ap; va_start(ap, fmt); - vim_vsnprintf((char *)IObuff, IOSIZE, fmt, ap, NULL); + vim_vsnprintf((char *)IObuff, IOSIZE, fmt, ap); va_end(ap); char *s = xstrdup((char *)IObuff); @@ -1826,7 +1833,7 @@ void msg_printf_attr(const int attr, const char *const fmt, ...) va_list ap; va_start(ap, fmt); - const size_t len = vim_vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap, NULL); + const size_t len = vim_vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); va_end(ap); msg_scroll = true; @@ -1873,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; } @@ -3001,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); } @@ -3341,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 a8cfc2d700..0008409731 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -710,9 +710,6 @@ open_line ( less_cols_off++; } } - if (*p_extra != NUL) { - did_ai = false; // append some text, don't truncate now - } /* columns for marks adjusted for removed columns */ less_cols = (int)(p_extra - saved_line); @@ -748,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 @@ -848,10 +845,11 @@ open_line ( /* Move marks after the line break to the new line. */ if (flags & OPENLINE_MARKFIX) mark_col_adjust(curwin->w_cursor.lnum, - curwin->w_cursor.col + less_cols_off, - 1L, (long)-less_cols); - } else + curwin->w_cursor.col + less_cols_off, + 1L, (long)-less_cols, 0); + } else { changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); + } } /* @@ -1749,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) @@ -2177,7 +2175,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra /* when the cursor line is changed always trigger CursorMoved */ if (lnum <= curwin->w_cursor.lnum && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) - last_cursormoved.lnum = 0; + curwin->w_last_cursormoved.lnum = 0; } /* @@ -2250,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(); @@ -2580,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) { @@ -2589,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 7aa7f922c1..e076543614 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -692,7 +692,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + (int)wp->w_p_fdc - + (signcolumn_on(wp) ? win_signcol_width(wp) : 0); + + (win_signcol_count(wp) * win_signcol_width(wp)); } int curwin_col_off(void) @@ -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) { @@ -2001,10 +2001,7 @@ static void get_scroll_overlap(lineoff_T *lp, int dir) return; } -/* #define KEEP_SCREEN_LINE */ -/* - * Scroll 'scroll' lines up or down. - */ +// Scroll 'scroll' lines up or down. void halfpage(bool flag, linenr_T Prenum) { long scrolled = 0; @@ -2039,13 +2036,11 @@ void halfpage(bool flag, linenr_T Prenum) ++curwin->w_topline; curwin->w_topfill = diff_check_fill(curwin, curwin->w_topline); -#ifndef KEEP_SCREEN_LINE if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { ++curwin->w_cursor.lnum; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } -#endif } curwin->w_valid &= ~(VALID_CROW|VALID_WROW); scrolled += i; @@ -2070,10 +2065,7 @@ void halfpage(bool flag, linenr_T Prenum) } } -#ifndef KEEP_SCREEN_LINE - /* - * When hit bottom of the file: move cursor down. - */ + // When hit bottom of the file: move cursor down. if (n > 0) { if (hasAnyFolding(curwin)) { while (--n >= 0 @@ -2086,18 +2078,6 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_cursor.lnum += n; check_cursor_lnum(); } -#else - /* try to put the cursor in the same screen line */ - while ((curwin->w_cursor.lnum < curwin->w_topline || scrolled > 0) - && curwin->w_cursor.lnum < curwin->w_botline - 1) { - scrolled -= plines(curwin->w_cursor.lnum); - if (scrolled < 0 && curwin->w_cursor.lnum >= curwin->w_topline) - break; - (void)hasFolding(curwin->w_cursor.lnum, NULL, - &curwin->w_cursor.lnum); - ++curwin->w_cursor.lnum; - } -#endif } else { /* * scroll the text down @@ -2119,17 +2099,13 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_valid &= ~(VALID_CROW|VALID_WROW| VALID_BOTLINE|VALID_BOTLINE_AP); scrolled += i; -#ifndef KEEP_SCREEN_LINE if (curwin->w_cursor.lnum > 1) { --curwin->w_cursor.lnum; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } -#endif } -#ifndef KEEP_SCREEN_LINE - /* - * When hit top of the file: move cursor up. - */ + + // When hit top of the file: move cursor up. if (n > 0) { if (curwin->w_cursor.lnum <= (linenr_T)n) curwin->w_cursor.lnum = 1; @@ -2142,18 +2118,6 @@ void halfpage(bool flag, linenr_T Prenum) } else curwin->w_cursor.lnum -= n; } -#else - /* try to put the cursor in the same screen line */ - scrolled += n; /* move cursor when topline is 1 */ - while (curwin->w_cursor.lnum > curwin->w_topline - && (scrolled > 0 || curwin->w_cursor.lnum >= curwin->w_botline)) { - scrolled -= plines(curwin->w_cursor.lnum - 1); - if (scrolled < 0 && curwin->w_cursor.lnum < curwin->w_botline) - break; - --curwin->w_cursor.lnum; - foldAdjustCursor(); - } -#endif } /* Move cursor to first line of closed fold. */ foldAdjustCursor(); diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 76fbe407c2..3438949e2d 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -131,7 +131,7 @@ Object rpc_send_call(uint64_t id, channel_incref(channel); RpcState *rpc = &channel->rpc; - uint64_t request_id = rpc->next_request_id++; + uint32_t request_id = rpc->next_request_id++; // Send the msgpack-rpc request send_request(channel, request_id, method_name, args); @@ -281,23 +281,26 @@ static void parse_msgpack(Channel *channel) // A not so uncommon cause for this might be deserializing objects with // a high nesting level: msgpack will break when its internal parse stack // size exceeds MSGPACK_EMBED_STACK_SIZE (defined as 32 by default) - send_error(channel, 0, "Invalid msgpack payload. " - "This error can also happen when deserializing " - "an object with high level of nesting"); + send_error(channel, kMessageTypeRequest, 0, + "Invalid msgpack payload. " + "This error can also happen when deserializing " + "an object with high level of nesting"); } } +/// Handles requests and notifications received on the channel. static void handle_request(Channel *channel, msgpack_object *request) FUNC_ATTR_NONNULL_ALL { - uint64_t request_id; + uint32_t request_id; Error error = ERROR_INIT; - msgpack_rpc_validate(&request_id, request, &error); + MessageType type = msgpack_rpc_validate(&request_id, request, &error); if (ERROR_SET(&error)) { // Validation failed, send response with error if (channel_write(channel, serialize_response(channel->id, + type, request_id, &error, NIL, @@ -311,6 +314,7 @@ static void handle_request(Channel *channel, msgpack_object *request) api_clear_error(&error); return; } + assert(type == kMessageTypeRequest || type == kMessageTypeNotification); MsgpackRpcRequestHandler handler; msgpack_object *method = msgpack_rpc_method(request); @@ -326,13 +330,14 @@ static void handle_request(Channel *channel, msgpack_object *request) } if (ERROR_SET(&error)) { - send_error(channel, request_id, error.msg); + send_error(channel, type, request_id, error.msg); api_clear_error(&error); api_free_array(args); return; } RequestEvent *evdata = xmalloc(sizeof(RequestEvent)); + evdata->type = type; evdata->channel = channel; evdata->handler = handler; evdata->args = args; @@ -343,39 +348,41 @@ static void handle_request(Channel *channel, msgpack_object *request) if (is_get_mode && !input_blocking()) { // Defer the event to a special queue used by os/input.c. #6247 - multiqueue_put(ch_before_blocking_events, on_request_event, 1, evdata); + multiqueue_put(ch_before_blocking_events, request_event, 1, evdata); } else { // Invoke immediately. - on_request_event((void **)&evdata); + request_event((void **)&evdata); } } else { - multiqueue_put(channel->events, on_request_event, 1, evdata); + multiqueue_put(channel->events, request_event, 1, evdata); DLOG("RPC: scheduled %.*s", method->via.bin.size, method->via.bin.ptr); } } -static void on_request_event(void **argv) +/// Handles a message, depending on the type: +/// - Request: invokes method and writes the response (or error). +/// - Notification: invokes method (emits `nvim_error_event` on error). +static void request_event(void **argv) { RequestEvent *e = argv[0]; Channel *channel = e->channel; MsgpackRpcRequestHandler handler = e->handler; - Array args = e->args; - uint64_t request_id = e->request_id; Error error = ERROR_INIT; - Object result = handler.fn(channel->id, args, &error); - if (request_id != NO_RESPONSE) { - // send the response + Object result = handler.fn(channel->id, e->args, &error); + if (e->type == kMessageTypeRequest || ERROR_SET(&error)) { + // Send the response. msgpack_packer response; msgpack_packer_init(&response, &out_buffer, msgpack_sbuffer_write); channel_write(channel, serialize_response(channel->id, - request_id, + e->type, + e->request_id, &error, result, &out_buffer)); } else { api_free_object(result); } - api_free_array(args); + api_free_array(e->args); channel_decref(channel); xfree(e); api_clear_error(&error); @@ -430,20 +437,21 @@ static void internal_read_event(void **argv) wstream_release_wbuffer(buffer); } -static void send_error(Channel *channel, uint64_t id, char *err) +static void send_error(Channel *chan, MessageType type, uint32_t id, char *err) { Error e = ERROR_INIT; api_set_error(&e, kErrorTypeException, "%s", err); - channel_write(channel, serialize_response(channel->id, - id, - &e, - NIL, - &out_buffer)); + channel_write(chan, serialize_response(chan->id, + type, + id, + &e, + NIL, + &out_buffer)); api_clear_error(&e); } static void send_request(Channel *channel, - uint64_t id, + uint32_t id, const char *name, Array args) { @@ -576,7 +584,7 @@ static bool is_rpc_response(msgpack_object *obj) static bool is_valid_rpc_response(msgpack_object *obj, Channel *channel) { - uint64_t response_id = obj->via.array.ptr[1].via.u64; + uint32_t response_id = (uint32_t)obj->via.array.ptr[1].via.u64; if (kv_size(channel->rpc.call_stack) == 0) { return false; } @@ -614,7 +622,7 @@ static void call_set_error(Channel *channel, char *msg, int loglevel) } static WBuffer *serialize_request(uint64_t channel_id, - uint64_t request_id, + uint32_t request_id, const String method, Array args, msgpack_sbuffer *sbuffer, @@ -634,14 +642,15 @@ static WBuffer *serialize_request(uint64_t channel_id, } static WBuffer *serialize_response(uint64_t channel_id, - uint64_t response_id, + MessageType type, + uint32_t response_id, Error *err, Object arg, msgpack_sbuffer *sbuffer) { msgpack_packer pac; msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); - if (ERROR_SET(err) && response_id == NO_RESPONSE) { + if (ERROR_SET(err) && type == kMessageTypeNotification) { Array args = ARRAY_DICT_INIT; ADD(args, INTEGER_OBJ(err->type)); ADD(args, STRING_OBJ(cstr_to_string(err->msg))); diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h index bfa7f7b87c..6ef8c027f0 100644 --- a/src/nvim/msgpack_rpc/channel_defs.h +++ b/src/nvim/msgpack_rpc/channel_defs.h @@ -13,23 +13,24 @@ typedef struct Channel Channel; typedef struct { - uint64_t request_id; + uint32_t request_id; bool returned, errored; Object result; } ChannelCallFrame; typedef struct { + MessageType type; Channel *channel; MsgpackRpcRequestHandler handler; Array args; - uint64_t request_id; + uint32_t request_id; } RequestEvent; typedef struct { PMap(cstr_t) *subscribed_events; bool closed; msgpack_unpacker *unpacker; - uint64_t next_request_id; + uint32_t next_request_id; kvec_t(ChannelCallFrame *) call_stack; Dictionary info; } RpcState; diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 19cc31f6a6..3925dc546a 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -489,7 +489,7 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) } /// Serializes a msgpack-rpc request or notification(id == 0) -void msgpack_rpc_serialize_request(uint64_t request_id, +void msgpack_rpc_serialize_request(uint32_t request_id, const String method, Array args, msgpack_packer *pac) @@ -499,7 +499,7 @@ void msgpack_rpc_serialize_request(uint64_t request_id, msgpack_pack_int(pac, request_id ? 0 : 2); if (request_id) { - msgpack_pack_uint64(pac, request_id); + msgpack_pack_uint32(pac, request_id); } msgpack_rpc_from_string(method, pac); @@ -507,7 +507,7 @@ void msgpack_rpc_serialize_request(uint64_t request_id, } /// Serializes a msgpack-rpc response -void msgpack_rpc_serialize_response(uint64_t response_id, +void msgpack_rpc_serialize_response(uint32_t response_id, Error *err, Object arg, msgpack_packer *pac) @@ -515,7 +515,7 @@ void msgpack_rpc_serialize_response(uint64_t response_id, { msgpack_pack_array(pac, 4); msgpack_pack_int(pac, 1); - msgpack_pack_uint64(pac, response_id); + msgpack_pack_uint32(pac, response_id); if (ERROR_SET(err)) { // error represented by a [type, message] array @@ -561,58 +561,57 @@ static msgpack_object *msgpack_rpc_msg_id(msgpack_object *req) return obj->type == MSGPACK_OBJECT_POSITIVE_INTEGER ? obj : NULL; } -void msgpack_rpc_validate(uint64_t *response_id, - msgpack_object *req, - Error *err) +MessageType msgpack_rpc_validate(uint32_t *response_id, msgpack_object *req, + Error *err) { - // response id not known yet - - *response_id = NO_RESPONSE; + *response_id = 0; // Validate the basic structure of the msgpack-rpc payload if (req->type != MSGPACK_OBJECT_ARRAY) { api_set_error(err, kErrorTypeValidation, "Message is not an array"); - return; + return kMessageTypeUnknown; } if (req->via.array.size == 0) { api_set_error(err, kErrorTypeValidation, "Message is empty"); - return; + return kMessageTypeUnknown; } if (req->via.array.ptr[0].type != MSGPACK_OBJECT_POSITIVE_INTEGER) { api_set_error(err, kErrorTypeValidation, "Message type must be an integer"); - return; + return kMessageTypeUnknown; } - uint64_t type = req->via.array.ptr[0].via.u64; + MessageType type = (MessageType)req->via.array.ptr[0].via.u64; if (type != kMessageTypeRequest && type != kMessageTypeNotification) { api_set_error(err, kErrorTypeValidation, "Unknown message type"); - return; + return kMessageTypeUnknown; } if ((type == kMessageTypeRequest && req->via.array.size != 4) || (type == kMessageTypeNotification && req->via.array.size != 3)) { api_set_error(err, kErrorTypeValidation, "Request array size must be 4 (request) or 3 (notification)"); - return; + return type; } if (type == kMessageTypeRequest) { msgpack_object *id_obj = msgpack_rpc_msg_id(req); if (!id_obj) { api_set_error(err, kErrorTypeValidation, "ID must be a positive integer"); - return; + return type; } - *response_id = id_obj->via.u64; + *response_id = (uint32_t)id_obj->via.u64; } if (!msgpack_rpc_method(req)) { api_set_error(err, kErrorTypeValidation, "Method must be a string"); - return; + return type; } if (!msgpack_rpc_args(req)) { api_set_error(err, kErrorTypeValidation, "Parameters must be an array"); - return; + return type; } + + return type; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f12abd362f..ca586cca29 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1187,12 +1187,12 @@ static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. if (!finish_op && (has_event(EVENT_CURSORMOVED) || curwin->w_p_cole > 0) - && !equalpos(last_cursormoved, curwin->w_cursor)) { + && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)) { if (has_event(EVENT_CURSORMOVED)) { apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); } - last_cursormoved = curwin->w_cursor; + curwin->w_last_cursormoved = curwin->w_cursor; } } @@ -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); } } @@ -6526,8 +6529,14 @@ static void n_start_visual_mode(int c) */ static void nv_window(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) - do_window(cap->nchar, cap->count0, NUL); /* everything is in window.c */ + if (cap->nchar == ':') { + // "CTRL-W :" is the same as typing ":"; useful in a terminal window + cap->cmdchar = ':'; + cap->nchar = NUL; + nv_colon(cap); + } else if (!checkclearop(cap->oap)) { + do_window(cap->nchar, cap->count0, NUL); // everything is in window.c + } } /* @@ -8001,7 +8010,6 @@ static void nv_event(cmdarg_T *cap) // lists or dicts being used. may_garbage_collect = false; multiqueue_process_events(main_loop.events); - cap->retval |= CA_COMMAND_BUSY; // don't call edit() now finish_op = false; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 99dee939fc..216bab4dda 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -153,6 +153,10 @@ int get_op_type(int char1, int char2) if (opchars[i][0] == char1 && opchars[i][1] == char2) { break; } + if (i == (int)(ARRAY_SIZE(opchars) - 1)) { + internal_error("get_op_type()"); + break; + } } return i; } @@ -1408,8 +1412,10 @@ int op_delete(oparg_T *oap) free_register(&y_regs[9]); /* free register "9 */ for (n = 9; n > 1; n--) y_regs[n] = y_regs[n - 1]; - y_previous = &y_regs[1]; - y_regs[1].y_array = NULL; /* set register "1 to empty */ + if (!is_append_register(oap->regname)) { + y_previous = &y_regs[1]; + } + y_regs[1].y_array = NULL; // set register "1 to empty reg = &y_regs[1]; op_yank_reg(oap, false, reg, false); } @@ -2605,17 +2611,16 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) static bool recursive = false; if (recursive || !has_event(EVENT_TEXTYANKPOST)) { - // No autocommand was defined - // or we yanked from this autocommand. + // No autocommand was defined, or we yanked from this autocommand. return; } recursive = true; - // set v:event to a dictionary with information about the yank + // Set the v:event dictionary with information about the yank. dict_T *dict = get_vim_var_dict(VV_EVENT); - // the yanked text + // The yanked text contents. list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); @@ -2623,17 +2628,21 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); - // the register type + // Register type. char buf[NUMBUFLEN+2]; format_reg_type(reg->y_type, reg->y_width, buf, ARRAY_SIZE(buf)); tv_dict_add_str(dict, S_LEN("regtype"), buf); - // name of requested register or the empty string for an unnamed operation. + // Name of requested register, or empty string for unnamed operation. buf[0] = (char)oap->regname; buf[1] = NUL; tv_dict_add_str(dict, S_LEN("regname"), buf); - // kind of operation (yank/delete/change) + // Motion type: inclusive or exclusive. + tv_dict_add_special(dict, S_LEN("inclusive"), + oap->inclusive ? kSpecialVarTrue : kSpecialVarFalse); + + // Kind of operation: yank, delete, change). buf[0] = (char)get_op_char(oap->op_type); buf[1] = NUL; tv_dict_add_str(dict, S_LEN("operator"), buf); @@ -2786,8 +2795,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } if (!curbuf->terminal) { - // Autocommands may be executed when saving lines for undo, which may make - // y_array invalid. Start undo now to avoid that. + // Autocommands may be executed when saving lines for undo. This might + // make y_array invalid, so we start undo now to avoid that. if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) { return; } @@ -2992,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(); @@ -3181,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); @@ -3704,10 +3714,16 @@ int do_join(size_t count, cend -= spaces[t]; memset(cend, ' ', (size_t)(spaces[t])); } + + // If deleting more spaces than adding, the cursor moves no more than + // what is added if it is inside these spaces. + const int spaces_removed = (int)((curr - curr_start) - spaces[t]); + mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t, - (long)(cend - newp + spaces[t] - (curr - curr_start))); - if (t == 0) + (long)(cend - newp - spaces_removed), spaces_removed); + if (t == 0) { break; + } curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1)); if (remove_comments) curr += comments[t - 1]; @@ -4135,14 +4151,14 @@ format_lines ( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len); + (long)-next_leader_len, 0); } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND int indent = (int)getwhitecols_curline(); if (indent > 0) { (void)del_bytes(indent, FALSE, FALSE); mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent); + (colnr_T)0, 0L, (long)-indent, 0); } } curwin->w_cursor.lnum--; @@ -5601,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 2346c84b54..743f6c8311 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -98,10 +98,10 @@ #define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x)) -/* WV_ and BV_ values get typecasted to this for the "indir" field */ +// WV_ and BV_ values get typecasted to this for the "indir" field typedef enum { PV_NONE = 0, - PV_MAXVAL = 0xffff /* to avoid warnings for value out of range */ + PV_MAXVAL = 0xffff // to avoid warnings for value out of range } idopt_T; /* @@ -175,7 +175,7 @@ static int p_udf; static long p_wm; static char_u *p_keymap; -/* Saved values for when 'bin' is set. */ +// Saved values for when 'bin' is set. static int p_et_nobin; static int p_ml_nobin; static long p_tw_nobin; @@ -202,28 +202,28 @@ typedef struct vimoption { # define SCRIPTID_INIT , 0 } vimoption_T; -#define VI_DEFAULT 0 /* def_val[VI_DEFAULT] is Vi default value */ -#define VIM_DEFAULT 1 /* def_val[VIM_DEFAULT] is Vim default value */ +#define VI_DEFAULT 0 // def_val[VI_DEFAULT] is Vi default value +#define VIM_DEFAULT 1 // def_val[VIM_DEFAULT] is Vim default value /* * Flags */ -#define P_BOOL 0x01U /* the option is boolean */ -#define P_NUM 0x02U /* the option is numeric */ -#define P_STRING 0x04U /* the option is a string */ -#define P_ALLOCED 0x08U /* the string option is in allocated memory, - must use free_string_option() when - assigning new value. Not set if default is - the same. */ -#define P_EXPAND 0x10U /* environment expansion. NOTE: P_EXPAND can - never be used for local or hidden options */ -#define P_NODEFAULT 0x40U /* don't set to default value */ -#define P_DEF_ALLOCED 0x80U /* default value is in allocated memory, must - use free() when assigning new value */ -#define P_WAS_SET 0x100U /* option has been set/reset */ -#define P_NO_MKRC 0x200U /* don't include in :mkvimrc output */ -#define P_VI_DEF 0x400U /* Use Vi default for Vim */ -#define P_VIM 0x800U /* Vim option */ +#define P_BOOL 0x01U // the option is boolean +#define P_NUM 0x02U // the option is numeric +#define P_STRING 0x04U // the option is a string +#define P_ALLOCED 0x08U // the string option is in allocated memory, + // must use free_string_option() when + // assigning new value. Not set if default is + // the same. +#define P_EXPAND 0x10U // environment expansion. NOTE: P_EXPAND can + // never be used for local or hidden options +#define P_NODEFAULT 0x40U // don't set to default value +#define P_DEF_ALLOCED 0x80U // default value is in allocated memory, must + // use free() when assigning new value +#define P_WAS_SET 0x100U // option has been set/reset +#define P_NO_MKRC 0x200U // don't include in :mkvimrc output +#define P_VI_DEF 0x400U // Use Vi default for Vim +#define P_VIM 0x800U // Vim option // when option changed, what to display: #define P_RSTAT 0x1000U ///< redraw status lines @@ -284,7 +284,6 @@ static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", NULL }; static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL }; -static char *(p_wop_values[]) = { "tagfile", NULL }; static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL }; @@ -306,7 +305,19 @@ static char *(p_fcl_values[]) = { "all", NULL }; static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "noinsert", "noselect", NULL }; static char *(p_icm_values[]) = { "nosplit", "split", NULL }; -static char *(p_scl_values[]) = { "yes", "no", "auto", NULL }; +static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", + "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9", + "yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8", + "yes:9", NULL }; + +/// All possible flags for 'shm'. +static char_u SHM_ALL[] = { + SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, + SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, + SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, SHM_COMPLETIONMENU, + SHM_RECORDING, SHM_FILEINFO, + 0, +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" @@ -421,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. @@ -441,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 @@ -450,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); @@ -471,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; @@ -488,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); } @@ -518,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; @@ -562,8 +593,8 @@ void set_init_1(void) langmap_init(); - /* Be nocompatible */ - p_cp = FALSE; + // Be nocompatible + p_cp = false; /* * Find default value for 'shell' option. @@ -590,23 +621,25 @@ void set_init_1(void) garray_T ga; ga_init(&ga, 1, 100); - for (size_t n = 0; n < ARRAY_SIZE(names); ++n) { + for (size_t n = 0; n < ARRAY_SIZE(names); n++) { bool mustfree = true; char *p; # ifdef UNIX if (*names[n] == NUL) { p = "/tmp"; mustfree = false; - } - else + } else # endif - p = vim_getenv(names[n]); + { + p = vim_getenv(names[n]); + } if (p != NULL && *p != NUL) { // First time count the NUL, otherwise count the ','. len = (int)strlen(p) + 3; ga_grow(&ga, len); - if (!GA_EMPTY(&ga)) + if (!GA_EMPTY(&ga)) { STRCAT(ga.ga_data, ","); + } STRCAT(ga.ga_data, p); add_pathsep(ga.ga_data); STRCAT(ga.ga_data, "*"); @@ -627,19 +660,20 @@ void set_init_1(void) int i; int j; - /* Initialize the 'cdpath' option's default value. */ + // Initialize the 'cdpath' option's default value. cdpath = (char_u *)vim_getenv("CDPATH"); if (cdpath != NULL) { buf = xmalloc(2 * STRLEN(cdpath) + 2); { - buf[0] = ','; /* start with ",", current dir first */ + buf[0] = ','; // start with ",", current dir first j = 1; - for (i = 0; cdpath[i] != NUL; ++i) { - if (vim_ispathlistsep(cdpath[i])) + for (i = 0; cdpath[i] != NUL; i++) { + if (vim_ispathlistsep(cdpath[i])) { buf[j++] = ','; - else { - if (cdpath[i] == ' ' || cdpath[i] == ',') + } else { + if (cdpath[i] == ' ' || cdpath[i] == ',') { buf[j++] = '\\'; + } buf[j++] = cdpath[i]; } } @@ -648,15 +682,16 @@ void set_init_1(void) if (opt_idx >= 0) { options[opt_idx].def_val[VI_DEFAULT] = buf; options[opt_idx].flags |= P_DEF_ALLOCED; - } else - xfree(buf); /* cannot happen */ + } else { + xfree(buf); // cannot happen + } } xfree(cdpath); } } #if defined(MSWIN) || defined(MAC) - /* Set print encoding on platforms that don't default to latin1 */ + // Set print encoding on platforms that don't default to latin1 set_string_default("printencoding", "hp-roman8", false); #endif @@ -701,19 +736,19 @@ void set_init_1(void) curbuf->b_p_initialized = true; - curbuf->b_p_ar = -1; /* no local 'autoread' value */ + curbuf->b_p_ar = -1; // no local 'autoread' value curbuf->b_p_ul = NO_LOCAL_UNDOLEVEL; check_buf_options(curbuf); check_win_options(curwin); check_options(); - /* Set all options to their Vim default */ + // Set all options to their Vim default set_options_default(OPT_FREE); // set 'laststatus' last_status(false); - /* Must be before option_expand(), because that one needs vim_isIDc() */ + // Must be before option_expand(), because that one needs vim_isIDc() didset_options(); // Use the current chartab for the generic chartab. This is not in @@ -748,14 +783,15 @@ void set_init_1(void) * and Vim. When this changes, add some code here! Also need to * split P_DEF_ALLOCED in two. */ - if (options[opt_idx].flags & P_DEF_ALLOCED) + if (options[opt_idx].flags & P_DEF_ALLOCED) { xfree(options[opt_idx].def_val[VI_DEFAULT]); - options[opt_idx].def_val[VI_DEFAULT] = (char_u *) p; + } + options[opt_idx].def_val[VI_DEFAULT] = (char_u *)p; options[opt_idx].flags |= P_DEF_ALLOCED; } } - save_file_ff(curbuf); /* Buffer is unchanged */ + save_file_ff(curbuf); // Buffer is unchanged /* Detect use of mlterm. * Mlterm is a terminal emulator akin to xterm that has some special @@ -787,7 +823,7 @@ void set_init_1(void) (void)bind_textdomain_codeset(PROJECT_NAME, (char *)p_enc); #endif - /* Set the default for 'helplang'. */ + // Set the default for 'helplang'. set_helplang_default(get_mess_lang()); } @@ -795,20 +831,20 @@ void set_init_1(void) * Set an option to its default value. * This does not take care of side effects! */ -static void -set_option_default ( +static void +set_option_default( int opt_idx, - int opt_flags, /* OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL */ - int compatible /* use Vi default value */ + int opt_flags, // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL + int compatible // use Vi default value ) { - char_u *varp; /* pointer to variable for current option */ - int dvi; /* index in def_val[] */ + char_u *varp; // pointer to variable for current option + int dvi; // index in def_val[] int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; varp = get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); uint32_t flags = options[opt_idx].flags; - if (varp != NULL) { /* skip hidden option, nothing to do for it */ + if (varp != NULL) { // skip hidden option, nothing to do for it dvi = ((flags & P_VI_DEF) || compatible) ? VI_DEFAULT : VIM_DEFAULT; if (flags & P_STRING) { /* Use set_string_option_direct() for local options to handle @@ -834,20 +870,22 @@ set_option_default ( *(long *)varp; } } - } else { /* P_BOOL */ + } else { // P_BOOL *(int *)varp = (int)(intptr_t)options[opt_idx].def_val[dvi]; #ifdef UNIX - /* 'modeline' defaults to off for root */ - if (options[opt_idx].indir == PV_ML && getuid() == ROOT_UID) - *(int *)varp = FALSE; + // 'modeline' defaults to off for root + if (options[opt_idx].indir == PV_ML && getuid() == ROOT_UID) { + *(int *)varp = false; + } #endif - /* May also set global value for local option. */ - if (both) + // May also set global value for local option. + if (both) { *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *(int *)varp; + } } - /* The default value is not insecure. */ + // The default value is not insecure. uint32_t *flagsp = insecure_flag(opt_idx, opt_flags); *flagsp = *flagsp & ~P_INSECURE; } @@ -858,9 +896,9 @@ set_option_default ( /* * Set all options (except terminal options) to their default value. */ -static void -set_options_default ( - int opt_flags /* OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL */ +static void +set_options_default( + int opt_flags // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL ) { for (int i = 0; options[i].fullname; i++) { @@ -869,7 +907,7 @@ set_options_default ( } } - /* The 'scroll' option must be computed for all windows. */ + // The 'scroll' option must be computed for all windows. FOR_ALL_TAB_WINDOWS(tp, wp) { win_comp_scroll(wp); } @@ -1056,8 +1094,9 @@ void set_helplang_default(const char *lang) } int idx = findoption("hlg"); if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { - if (options[idx].flags & P_ALLOCED) + if (options[idx].flags & P_ALLOCED) { free_string_option(p_hlg); + } p_hlg = (char_u *)xmemdupz(lang, lang_len); // zh_CN becomes "cn", zh_TW becomes "tw". if (STRNICMP(p_hlg, "zh_", 3) == 0 && STRLEN(p_hlg) >= 5) { @@ -1102,24 +1141,21 @@ void set_title_defaults(void) } } -/* - * Parse 'arg' for option settings. - * - * 'arg' may be IObuff, but only when no errors can be present and option - * does not need to be expanded with option_expand(). - * "opt_flags": - * 0 for ":set" - * OPT_GLOBAL for ":setglobal" - * OPT_LOCAL for ":setlocal" and a modeline - * OPT_MODELINE for a modeline - * OPT_WINONLY to only set window-local options - * OPT_NOWIN to skip setting window-local options - * - * returns FAIL if an error is detected, OK otherwise - */ -int -do_set ( - char_u *arg, /* option string (may be written to!) */ +// Parse 'arg' for option settings. +// +// 'arg' may be IObuff, but only when no errors can be present and option +// does not need to be expanded with option_expand(). +// "opt_flags": +// 0 for ":set" +// OPT_GLOBAL for ":setglobal" +// OPT_LOCAL for ":setlocal" and a modeline +// OPT_MODELINE for a modeline +// OPT_WINONLY to only set window-local options +// OPT_NOWIN to skip setting window-local options +// +// returns FAIL if an error is detected, OK otherwise +int do_set( + char_u *arg, // option string (may be written to!) int opt_flags ) { @@ -1127,30 +1163,30 @@ do_set ( char_u *errmsg; char_u errbuf[80]; char_u *startarg; - int prefix; /* 1: nothing, 0: "no", 2: "inv" in front of name */ - char_u nextchar; /* next non-white char after option name */ - int afterchar; /* character just after option name */ + int prefix; // 1: nothing, 0: "no", 2: "inv" in front of name + char_u nextchar; // next non-white char after option name + int afterchar; // character just after option name int len; int i; varnumber_T value; int key; - uint32_t flags; /* flags for current option */ - char_u *varp = NULL; /* pointer to variable for current option */ - int did_show = FALSE; /* already showed one value */ - int adding; /* "opt+=arg" */ - int prepending; /* "opt^=arg" */ - int removing; /* "opt-=arg" */ + uint32_t flags; // flags for current option + char_u *varp = NULL; // pointer to variable for current option + int did_show = false; // already showed one value + int adding; // "opt+=arg" + int prepending; // "opt^=arg" + int removing; // "opt-=arg" int cp_val = 0; if (*arg == NUL) { showoptions(0, opt_flags); - did_show = TRUE; + did_show = true; goto theend; } - while (*arg != NUL) { /* loop to process all options */ + while (*arg != NUL) { // loop to process all options errmsg = NULL; - startarg = arg; /* remember for error message */ + startarg = arg; // remember for error message if (STRNCMP(arg, "all", 3) == 0 && !isalpha(arg[3]) && !(opt_flags & OPT_MODELINE)) { @@ -1169,7 +1205,7 @@ do_set ( redraw_all_later(CLEAR); } else { showoptions(1, opt_flags); - did_show = TRUE; + did_show = true; } } else { prefix = 1; @@ -1181,17 +1217,18 @@ do_set ( arg += 3; } - /* find end of name */ + // find end of name key = 0; if (*arg == '<') { opt_idx = -1; - /* look out for <t_>;> */ - if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4]) + // look out for <t_>;> + if (arg[1] == 't' && arg[2] == '_' && arg[3] && arg[4]) { len = 5; - else { + } else { len = 1; - while (arg[len] != NUL && arg[len] != '>') - ++len; + while (arg[len] != NUL && arg[len] != '>') { + len++; + } } if (arg[len] != '>') { errmsg = e_invarg; @@ -1220,43 +1257,45 @@ do_set ( } } - /* remember character after option name */ + // remember character after option name afterchar = arg[len]; - /* skip white space, allow ":set ai ?" */ - while (ascii_iswhite(arg[len])) - ++len; + // skip white space, allow ":set ai ?" + while (ascii_iswhite(arg[len])) { + len++; + } - adding = FALSE; - prepending = FALSE; - removing = FALSE; + adding = false; + prepending = false; + removing = false; if (arg[len] != NUL && arg[len + 1] == '=') { if (arg[len] == '+') { - adding = TRUE; /* "+=" */ - ++len; + adding = true; // "+=" + len++; } else if (arg[len] == '^') { - prepending = TRUE; /* "^=" */ - ++len; + prepending = true; // "^=" + len++; } else if (arg[len] == '-') { - removing = TRUE; /* "-=" */ - ++len; + removing = true; // "-=" + len++; } } nextchar = arg[len]; - if (opt_idx == -1 && key == 0) { /* found a mismatch: skip */ + if (opt_idx == -1 && key == 0) { // found a mismatch: skip errmsg = (char_u *)N_("E518: Unknown option"); goto skip; } if (opt_idx >= 0) { - if (options[opt_idx].var == NULL) { /* hidden option: skip */ - /* Only give an error message when requesting the value of - * a hidden option, ignore setting it. */ + if (options[opt_idx].var == NULL) { // hidden option: skip + // Only give an error message when requesting the value of + // a hidden option, ignore setting it. if (vim_strchr((char_u *)"=:!&<", nextchar) == NULL && (!(options[opt_idx].flags & P_BOOL) - || nextchar == '?')) + || nextchar == '?')) { errmsg = (char_u *)_(e_unsupportedoption); + } goto skip; } @@ -1272,28 +1311,30 @@ do_set ( && (opt_idx < 0 || options[opt_idx].var != VAR_WIN)) goto skip; - /* Skip all options that are window-local (used for :vimgrep). */ + // Skip all options that are window-local (used for :vimgrep). if ((opt_flags & OPT_NOWIN) && opt_idx >= 0 - && options[opt_idx].var == VAR_WIN) + && options[opt_idx].var == VAR_WIN) { goto skip; + } - /* Disallow changing some options from modelines. */ + // Disallow changing some options from modelines. if (opt_flags & OPT_MODELINE) { if (flags & (P_SECURE | P_NO_ML)) { errmsg = (char_u *)_("E520: Not allowed in a modeline"); goto skip; } - /* In diff mode some options are overruled. This avoids that - * 'foldmethod' becomes "marker" instead of "diff" and that - * "wrap" gets set. */ + // In diff mode some options are overruled. This avoids that + // 'foldmethod' becomes "marker" instead of "diff" and that + // "wrap" gets set. if (curwin->w_p_diff - && opt_idx >= 0 /* shut up coverity warning */ + && opt_idx >= 0 // shut up coverity warning && (options[opt_idx].indir == PV_FDM - || options[opt_idx].indir == PV_WRAP)) + || options[opt_idx].indir == PV_WRAP)) { goto skip; + } } - /* Disallow changing some options in the sandbox */ + // Disallow changing some options in the sandbox if (sandbox != 0 && (flags & P_SECURE)) { errmsg = (char_u *)_(e_sandbox); goto skip; @@ -1303,11 +1344,11 @@ do_set ( arg += len; cp_val = p_cp; if (nextchar == '&' && arg[1] == 'v' && arg[2] == 'i') { - if (arg[3] == 'm') { /* "opt&vim": set to Vim default */ - cp_val = FALSE; + if (arg[3] == 'm') { // "opt&vim": set to Vim default + cp_val = false; arg += 3; - } else { /* "opt&vi": set to Vi default */ - cp_val = TRUE; + } else { // "opt&vi": set to Vi default + cp_val = true; arg += 2; } } @@ -1329,11 +1370,11 @@ do_set ( /* * print value */ - if (did_show) - msg_putchar('\n'); /* cursor below last one */ - else { - gotocmdline(TRUE); /* cursor at status line */ - did_show = TRUE; /* remember that we did a line */ + if (did_show) { + msg_putchar('\n'); // cursor below last one + } else { + gotocmdline(true); // cursor at status line + did_show = true; // remember that we did a line } if (opt_idx >= 0) { showoneopt(&options[opt_idx], opt_flags); @@ -1357,7 +1398,10 @@ do_set ( && nextchar != NUL && !ascii_iswhite(afterchar)) errmsg = e_trailing; } else { - if (flags & P_BOOL) { /* boolean */ + int value_is_replaced = !prepending && !adding && !removing; + int value_checked = false; + + if (flags & P_BOOL) { // boolean if (nextchar == '=' || nextchar == ':') { errmsg = e_invarg; goto skip; @@ -1368,20 +1412,21 @@ do_set ( * ":set opt&": reset to default value * ":set opt<": reset to global value */ - if (nextchar == '!') + if (nextchar == '!') { value = *(int *)(varp) ^ 1; - else if (nextchar == '&') + } else if (nextchar == '&') { value = (int)(intptr_t)options[opt_idx].def_val[ - ((flags & P_VI_DEF) || cp_val) - ? VI_DEFAULT : VIM_DEFAULT]; - else if (nextchar == '<') { - /* For 'autoread' -1 means to use global value. */ + ((flags & P_VI_DEF) || cp_val) + ? VI_DEFAULT : VIM_DEFAULT]; + } else if (nextchar == '<') { + // For 'autoread' -1 means to use global value. if ((int *)varp == &curbuf->b_p_ar - && opt_flags == OPT_LOCAL) + && opt_flags == OPT_LOCAL) { value = -1; - else + } else { value = *(int *)get_varp_scope(&(options[opt_idx]), - OPT_GLOBAL); + OPT_GLOBAL); + } } else { /* * ":set invopt": invert @@ -1391,10 +1436,11 @@ do_set ( errmsg = e_trailing; goto skip; } - if (prefix == 2) /* inv */ + if (prefix == 2) { // inv value = *(int *)(varp) ^ 1; - else + } else { value = prefix; + } } errmsg = (char_u *)set_bool_option(opt_idx, varp, (int)value, @@ -1406,16 +1452,14 @@ do_set ( goto skip; } - if (flags & P_NUM) { /* numeric */ - /* - * Different ways to set a number option: - * & set to default value - * < set to global value - * <xx> accept special key codes for 'wildchar' - * c accept any non-digit for 'wildchar' - * [-]0-9 set number - * other error - */ + if (flags & P_NUM) { // numeric + // Different ways to set a number option: + // & set to default value + // < set to global value + // <xx> accept special key codes for 'wildchar' + // c accept any non-digit for 'wildchar' + // [-]0-9 set number + // other error arg++; if (nextchar == '&') { value = (long)(intptr_t)options[opt_idx].def_val[ @@ -1520,10 +1564,10 @@ do_set ( } } else if (nextchar == '<') { // set to global val newval = vim_strsave(*(char_u **)get_varp_scope( - &(options[opt_idx]), OPT_GLOBAL)); - new_value_alloced = TRUE; + &(options[opt_idx]), OPT_GLOBAL)); + new_value_alloced = true; } else { - ++arg; /* jump to after the '=' or ':' */ + arg++; // jump to after the '=' or ':' /* * Set 'keywordprg' to ":help" if an empty @@ -1571,18 +1615,24 @@ do_set ( && ascii_isdigit(*arg)) { *errbuf = NUL; i = getdigits_int(&arg); - if (i & 1) + if (i & 1) { STRCAT(errbuf, "b,"); - if (i & 2) + } + if (i & 2) { STRCAT(errbuf, "s,"); - if (i & 4) + } + if (i & 4) { STRCAT(errbuf, "h,l,"); - if (i & 8) + } + if (i & 8) { STRCAT(errbuf, "<,>,"); - if (i & 16) + } + if (i & 16) { STRCAT(errbuf, "[,],"); - if (*errbuf != NUL) /* remove trailing , */ + } + if (*errbuf != NUL) { // remove trailing , errbuf[STRLEN(errbuf) - 1] = NUL; + } save_arg = arg; arg = errbuf; } @@ -1593,7 +1643,7 @@ do_set ( else if ( *arg == '>' && (varp == (char_u *)&p_dir || varp == (char_u *)&p_bdir)) { - ++arg; + arg++; } /* @@ -1601,10 +1651,11 @@ do_set ( * Can't use set_string_option_direct(), because * we need to remove the backslashes. */ - /* get a bit too much */ + // get a bit too much newlen = (unsigned)STRLEN(arg) + 1; - if (adding || prepending || removing) + if (adding || prepending || removing) { newlen += (unsigned)STRLEN(origval) + 1; + } newval = xmalloc(newlen); s = newval; @@ -1626,10 +1677,10 @@ do_set ( && arg[2] != '\\'))) #endif ) - ++arg; /* remove backslash */ + arg++; // remove backslash if (has_mbyte && (i = (*mb_ptr2len)(arg)) > 1) { - /* copy multibyte char */ + // copy multibyte char memmove(s, arg, (size_t)i); arg += i; s += i; @@ -1649,8 +1700,9 @@ do_set ( if (s != NULL) { xfree(newval); newlen = (unsigned)STRLEN(s) + 1; - if (adding || prepending || removing) + if (adding || prepending || removing) { newlen += (unsigned)STRLEN(origval) + 1; + } newval = xmalloc(newlen); STRCPY(newval, s); } @@ -1658,11 +1710,11 @@ do_set ( /* locate newval[] in origval[] when removing it * and when adding to avoid duplicates */ - i = 0; /* init for GCC */ + i = 0; // init for GCC if (removing || (flags & P_NODUP)) { i = (int)STRLEN(newval); bs = 0; - for (s = origval; *s; ++s) { + for (s = origval; *s; s++) { if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) @@ -1685,8 +1737,8 @@ do_set ( // do not add if already there if ((adding || prepending) && *s) { - prepending = FALSE; - adding = FALSE; + prepending = false; + adding = false; STRCPY(newval, origval); } } @@ -1712,8 +1764,9 @@ do_set ( i = (int)STRLEN(newval); STRMOVE(newval + i + comma, origval); } - if (comma) + if (comma) { newval[i] = ','; + } } /* Remove newval[] from origval[]. (Note: "i" has @@ -1721,16 +1774,17 @@ do_set ( if (removing) { STRCPY(newval, origval); if (*s) { - /* may need to remove a comma */ + // may need to remove a comma if (flags & P_COMMA) { if (s == origval) { - /* include comma after string */ - if (s[i] == ',') - ++i; + // include comma after string + if (s[i] == ',') { + i++; + } } else { - /* include comma before string */ - --s; - ++i; + // include comma before string + s--; + i++; } } STRMOVE(newval + (s - origval), s + i); @@ -1760,9 +1814,10 @@ do_set ( } } - if (save_arg != NULL) /* number for 'whichwrap' */ + if (save_arg != NULL) { // number for 'whichwrap' arg = save_arg; - new_value_alloced = TRUE; + } + new_value_alloced = true; } // Set the new value. @@ -1776,12 +1831,32 @@ do_set ( // buffer is closed by autocommands. saved_newval = (newval != NULL) ? xstrdup((char *)newval) : 0; - // 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, opt_flags); + { + uint32_t *p = insecure_flag(opt_idx, opt_flags); + 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 ((opt_flags & OPT_MODELINE) + || sandbox != 0 + || (!value_is_replaced && (*p & P_INSECURE))) { + 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. + errmsg = did_set_string_option(opt_idx, (char_u **)varp, + new_value_alloced, oldval, + errbuf, sizeof(errbuf), + opt_flags, &value_checked); + + secure = secure_saved; + } if (errmsg == NULL) { if (!starting) { @@ -1807,9 +1882,9 @@ do_set ( } } - if (opt_idx >= 0) - did_set_option(opt_idx, opt_flags, - !prepending && !adding && !removing); + if (opt_idx >= 0) { + did_set_option(opt_idx, opt_flags, value_is_replaced, value_checked); + } } skip: @@ -1819,13 +1894,16 @@ skip: * - skip blanks * - skip one "=val" argument (for hidden options ":set gfn =xx") */ - for (i = 0; i < 2; ++i) { - while (*arg != NUL && !ascii_iswhite(*arg)) - if (*arg++ == '\\' && *arg != NUL) - ++arg; + for (i = 0; i < 2; i++) { + while (*arg != NUL && !ascii_iswhite(*arg)) { + if (*arg++ == '\\' && *arg != NUL) { + arg++; + } + } arg = skipwhite(arg); - if (*arg != '=') + if (*arg != '=') { break; + } } } @@ -1833,18 +1911,18 @@ skip: STRLCPY(IObuff, _(errmsg), IOSIZE); i = (int)STRLEN(IObuff) + 2; if (i + (arg - startarg) < IOSIZE) { - /* append the argument with the error */ + // append the argument with the error STRCAT(IObuff, ": "); assert(arg >= startarg); memmove(IObuff + i, startarg, (size_t)(arg - startarg)); IObuff[i + (arg - startarg)] = NUL; } - /* make sure all characters are printable */ + // make sure all characters are printable trans_characters(IObuff, IOSIZE); - ++no_wait_return; /* wait_return done later */ - emsg(IObuff); /* show error highlighted */ - --no_wait_return; + no_wait_return++; // wait_return done later + emsg(IObuff); // show error highlighted + no_wait_return--; return FAIL; } @@ -1854,27 +1932,26 @@ skip: theend: if (silent_mode && did_show) { - /* After displaying option values in silent mode. */ - silent_mode = FALSE; - info_message = TRUE; /* use mch_msg(), not mch_errmsg() */ + // After displaying option values in silent mode. + silent_mode = false; + info_message = true; // use mch_msg(), not mch_errmsg() msg_putchar('\n'); ui_flush(); - silent_mode = TRUE; - info_message = FALSE; /* use mch_msg(), not mch_errmsg() */ + silent_mode = true; + info_message = false; // use mch_msg(), not mch_errmsg() } return OK; } -/* - * Call this when an option has been given a new value through a user command. - * Sets the P_WAS_SET flag and takes care of the P_INSECURE flag. - */ -static void -did_set_option ( +// Call this when an option has been given a new value through a user command. +// Sets the P_WAS_SET flag and takes care of the P_INSECURE flag. +static void did_set_option( int opt_idx, - int opt_flags, /* possibly with OPT_MODELINE */ - int new_value /* value was replaced completely */ + int opt_flags, // possibly with OPT_MODELINE + int new_value, // value was replaced completely + int value_checked // value was checked to be safe, no need to + // set P_INSECURE ) { options[opt_idx].flags |= P_WAS_SET; @@ -1883,20 +1960,22 @@ did_set_option ( * set the P_INSECURE flag. Otherwise, if a new value is stored reset the * flag. */ uint32_t *p = insecure_flag(opt_idx, opt_flags); - if (secure - || sandbox != 0 - || (opt_flags & OPT_MODELINE)) + if (!value_checked && (secure + || sandbox != 0 + || (opt_flags & OPT_MODELINE))) { *p = *p | P_INSECURE; - else if (new_value) + } else if (new_value) { *p = *p & ~P_INSECURE; + } } -static char_u *illegal_char(char_u *errbuf, int c) +static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c) { - if (errbuf == NULL) + if (errbuf == NULL) { return (char_u *)""; - sprintf((char *)errbuf, _("E539: Illegal character <%s>"), - (char *)transchar(c)); + } + vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"), + (char *)transchar(c)); return errbuf; } @@ -1906,10 +1985,12 @@ static char_u *illegal_char(char_u *errbuf, int c) */ static int string_to_key(char_u *arg) { - if (*arg == '<') + if (*arg == '<') { return find_key_option(arg + 1); - if (*arg == '^') + } + if (*arg == '^') { return Ctrl_chr(arg[1]); + } return *arg; } @@ -1921,26 +2002,24 @@ static char_u *check_cedit(void) { int n; - if (*p_cedit == NUL) + if (*p_cedit == NUL) { cedit_key = -1; - else { + } else { n = string_to_key(p_cedit); - if (vim_isprintc(n)) + if (vim_isprintc(n)) { return e_invarg; + } cedit_key = n; } return NULL; } -/* - * When changing 'title', 'titlestring', 'icon' or 'iconstring', call - * maketitle() to create and display it. - * When switching the title or icon off, call ui_set_{icon,title}(NULL) to get - * the old value back. - */ -static void -did_set_title ( - int icon /* Did set icon instead of title */ +// When changing 'title', 'titlestring', 'icon' or 'iconstring', call +// maketitle() to create and display it. +// When switching the title or icon off, call ui_set_{icon,title}(NULL) to get +// the old value back. +static void did_set_title( + int icon // Did set icon instead of title ) { if (starting != NO_SCREEN) { @@ -1949,14 +2028,11 @@ did_set_title ( } } -/* - * set_options_bin - called when 'bin' changes value. - */ -void -set_options_bin ( +// set_options_bin - called when 'bin' changes value. +void set_options_bin( int oldval, int newval, - int opt_flags /* OPT_LOCAL and/or OPT_GLOBAL */ + int opt_flags // OPT_LOCAL and/or OPT_GLOBAL ) { /* @@ -1964,7 +2040,7 @@ set_options_bin ( * copied when 'bin is set and restored when 'bin' is reset. */ if (newval) { - if (!oldval) { /* switched on */ + if (!oldval) { // switched on if (!(opt_flags & OPT_GLOBAL)) { curbuf->b_p_tw_nobin = curbuf->b_p_tw; curbuf->b_p_wm_nobin = curbuf->b_p_wm; @@ -1980,19 +2056,19 @@ set_options_bin ( } if (!(opt_flags & OPT_GLOBAL)) { - curbuf->b_p_tw = 0; /* no automatic line wrap */ - curbuf->b_p_wm = 0; /* no automatic line wrap */ - curbuf->b_p_ml = 0; /* no modelines */ - curbuf->b_p_et = 0; /* no expandtab */ + curbuf->b_p_tw = 0; // no automatic line wrap + curbuf->b_p_wm = 0; // no automatic line wrap + curbuf->b_p_ml = 0; // no modelines + curbuf->b_p_et = 0; // no expandtab } if (!(opt_flags & OPT_LOCAL)) { p_tw = 0; p_wm = 0; - p_ml = FALSE; - p_et = FALSE; - p_bin = TRUE; /* needed when called for the "-b" argument */ + p_ml = false; + p_et = false; + p_bin = true; // needed when called for the "-b" argument } - } else if (oldval) { /* switched off */ + } else if (oldval) { // switched off if (!(opt_flags & OPT_GLOBAL)) { curbuf->b_p_tw = curbuf->b_p_tw_nobin; curbuf->b_p_wm = curbuf->b_p_wm_nobin; @@ -2020,8 +2096,9 @@ int get_shada_parameter(int type) char_u *p; p = find_shada_parameter(type); - if (p != NULL && ascii_isdigit(*p)) + if (p != NULL && ascii_isdigit(*p)) { return atoi((char *)p); + } return -1; } @@ -2034,14 +2111,17 @@ char_u *find_shada_parameter(int type) { char_u *p; - for (p = p_shada; *p; ++p) { - if (*p == type) + for (p = p_shada; *p; p++) { + if (*p == type) { return p + 1; - if (*p == 'n') /* 'n' is always the last one */ + } + if (*p == 'n') { // 'n' is always the last one break; - p = vim_strchr(p, ','); /* skip until next ',' */ - if (p == NULL) /* hit the end without finding parameter */ + } + p = vim_strchr(p, ','); // skip until next ',' + if (p == NULL) { // hit the end without finding parameter break; + } } return NULL; } @@ -2054,9 +2134,10 @@ char_u *find_shada_parameter(int type) */ static char_u *option_expand(int opt_idx, char_u *val) { - /* if option doesn't need expansion nothing to do */ - if (!(options[opt_idx].flags & P_EXPAND) || options[opt_idx].var == NULL) + // if option doesn't need expansion nothing to do + if (!(options[opt_idx].flags & P_EXPAND) || options[opt_idx].var == NULL) { return NULL; + } if (val == NULL) { val = *(char_u **)options[opt_idx].var; @@ -2075,11 +2156,12 @@ static char_u *option_expand(int opt_idx, char_u *val) * For 'spellsuggest' expand after "file:". */ expand_env_esc(val, NameBuff, MAXPATHL, - (char_u **)options[opt_idx].var == &p_tags, FALSE, - (char_u **)options[opt_idx].var == &p_sps ? (char_u *)"file:" : - NULL); - if (STRCMP(NameBuff, val) == 0) /* they are the same */ + (char_u **)options[opt_idx].var == &p_tags, false, + (char_u **)options[opt_idx].var == &p_sps ? (char_u *)"file:" : + NULL); + if (STRCMP(NameBuff, val) == 0) { // they are the same return NULL; + } return NameBuff; } @@ -2090,7 +2172,7 @@ static char_u *option_expand(int opt_idx, char_u *val) */ static void didset_options(void) { - /* initialize the table for 'iskeyword' et.al. */ + // initialize the table for 'iskeyword' et.al. (void)init_chartab(); (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); @@ -2139,9 +2221,11 @@ void check_options(void) { int opt_idx; - for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) - if ((options[opt_idx].flags & P_STRING) && options[opt_idx].var != NULL) + for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) { + if ((options[opt_idx].flags & P_STRING) && options[opt_idx].var != NULL) { check_string_option((char_u **)get_varp(&(options[opt_idx]))); + } + } } /* @@ -2207,28 +2291,29 @@ void check_buf_options(buf_T *buf) */ void free_string_option(char_u *p) { - if (p != empty_option) + if (p != empty_option) { xfree(p); + } } void clear_string_option(char_u **pp) { - if (*pp != empty_option) + if (*pp != empty_option) { xfree(*pp); + } *pp = empty_option; } static void check_string_option(char_u **pp) { - if (*pp == NULL) + if (*pp == NULL) { *pp = empty_option; + } } -/* - * Return TRUE when option "opt" was set from a modeline or in secure mode. - * Return FALSE when it wasn't. - * Return -1 for an unknown option. - */ +/// Return true when option "opt" was set from a modeline or in secure mode. +/// Return false when it wasn't. +/// Return -1 for an unknown option. int was_set_insecurely(char_u *opt, int opt_flags) { int idx = findoption((const char *)opt); @@ -2257,7 +2342,7 @@ static uint32_t *insecure_flag(int opt_idx, int opt_flags) case PV_INEX: return &curbuf->b_p_inex_flags; } - /* Nothing special, return global flags field. */ + // Nothing special, return global flags field. return &options[opt_idx].flags; } @@ -2265,9 +2350,10 @@ static uint32_t *insecure_flag(int opt_idx, int opt_flags) /* * Redraw the window title and/or tab page text later. */ -static void redraw_titles(void) { - need_maketitle = TRUE; - redraw_tabline = TRUE; +static void redraw_titles(void) +{ + need_maketitle = true; + redraw_tabline = true; } static int shada_idx = -1; @@ -2279,12 +2365,12 @@ static int shada_idx = -1; * When "set_sid" is zero set the scriptID to current_SID. When "set_sid" is * SID_NONE don't set the scriptID. Otherwise set the scriptID to "set_sid". */ -void -set_string_option_direct ( +void +set_string_option_direct( char_u *name, int opt_idx, char_u *val, - int opt_flags, /* OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL */ + int opt_flags, // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL int set_sid ) { @@ -2302,22 +2388,25 @@ set_string_option_direct ( } } - if (options[idx].var == NULL) /* can't set hidden option */ + if (options[idx].var == NULL) { // can't set hidden option return; + } assert((void *) options[idx].var != (void *) &p_shada); s = vim_strsave(val); { varp = (char_u **)get_varp_scope(&(options[idx]), - both ? OPT_LOCAL : opt_flags); - if ((opt_flags & OPT_FREE) && (options[idx].flags & P_ALLOCED)) + both ? OPT_LOCAL : opt_flags); + if ((opt_flags & OPT_FREE) && (options[idx].flags & P_ALLOCED)) { free_string_option(*varp); + } *varp = s; - /* For buffer/window local option may also set the global value. */ - if (both) + // For buffer/window local option may also set the global value. + if (both) { set_string_option_global(idx, varp); + } options[idx].flags |= P_ALLOCED; @@ -2336,19 +2425,20 @@ set_string_option_direct ( /* * Set global value for string option when it's a local option. */ -static void -set_string_option_global ( - int opt_idx, /* option index */ - char_u **varp /* pointer to option variable */ +static void +set_string_option_global( + int opt_idx, // option index + char_u **varp // pointer to option variable ) { char_u **p, *s; - /* the global value is always allocated */ - if (options[opt_idx].var == VAR_WIN) + // the global value is always allocated + if (options[opt_idx].var == VAR_WIN) { p = (char_u **)GLOBAL_WO(varp); - else + } else { p = (char_u **)options[opt_idx].var; + } if (options[opt_idx].indir != PV_NONE && p != varp) { s = vim_strsave(*varp); free_string_option(*p); @@ -2385,10 +2475,12 @@ static char *set_string_option(const int opt_idx, const char *const value, char *const saved_oldval = xstrdup(oldval); char *const saved_newval = xstrdup(s); + int value_checked = false; char *const r = (char *)did_set_string_option( - opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, NULL, opt_flags); + opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, + NULL, 0, opt_flags, &value_checked); if (r == NULL) { - did_set_option(opt_idx, opt_flags, true); + did_set_option(opt_idx, opt_flags, true, value_checked); } // call autocommand after handling side effects @@ -2419,28 +2511,24 @@ static bool valid_filetype(char_u *val) return true; } -#ifdef _MSC_VER -// MSVC optimizations are disabled for this function because it -// incorrectly generates an empty string for SHM_ALL. -#pragma optimize("", off) -#endif -/* - * Handle string options that need some action to perform when changed. - * Returns NULL for success, or an error message for an error. - */ +/// Handle string options that need some action to perform when changed. +/// Returns NULL for success, or an error message for an error. static char_u * -did_set_string_option ( - int opt_idx, /* index in options[] table */ - char_u **varp, /* pointer to the option variable */ - int new_value_alloced, /* new value was allocated */ - char_u *oldval, /* previous value of the option */ - char_u *errbuf, /* buffer for errors, or NULL */ - int opt_flags /* OPT_LOCAL and/or OPT_GLOBAL */ +did_set_string_option( + int opt_idx, // index in options[] table + char_u **varp, // pointer to the option variable + int new_value_alloced, // new value was allocated + char_u *oldval, // previous value of the option + char_u *errbuf, // buffer for errors, or NULL + size_t errbuflen, // length of errors buffer + int opt_flags, // OPT_LOCAL and/or OPT_GLOBAL + int *value_checked // value was checked to be safe, no + // need to set P_INSECURE ) { char_u *errmsg = NULL; char_u *s, *p; - int did_chartab = FALSE; + int did_chartab = false; char_u **gvarp; bool free_oldval = (options[opt_idx].flags & P_ALLOCED); bool value_changed = false; @@ -2449,7 +2537,7 @@ did_set_string_option ( * two values for all local options. */ gvarp = (char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - /* Disallow changing some options from secure mode */ + // Disallow changing some options from secure mode if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { errmsg = e_secure; @@ -2462,9 +2550,7 @@ did_set_string_option ( // path separator (slash and/or backslash), wildcards and characters that // are often illegal in a file name. Be more permissive if "secure" is off. errmsg = e_invarg; - } - /* 'backupcopy' */ - else if (gvarp == &p_bkc) { + } else if (gvarp == &p_bkc) { // 'backupcopy' char_u *bkc = p_bkc; unsigned int *flags = &bkc_flags; @@ -2489,17 +2575,15 @@ did_set_string_option ( errmsg = e_invarg; } } - } - /* 'backupext' and 'patchmode' */ - else if (varp == &p_bex || varp == &p_pm) { + } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode' if (STRCMP(*p_bex == '.' ? p_bex + 1 : p_bex, - *p_pm == '.' ? p_pm + 1 : p_pm) == 0) + *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { errmsg = (char_u *)N_("E589: 'backupext' and 'patchmode' are equal"); - } - /* 'breakindentopt' */ - else if (varp == &curwin->w_p_briopt) { - if (briopt_check(curwin) == FAIL) + } + } else if (varp == &curwin->w_p_briopt) { // 'breakindentopt' + if (briopt_check(curwin) == FAIL) { errmsg = e_invarg; + } } else if (varp == &p_isi || varp == &(curbuf->b_p_isk) || varp == &p_isp @@ -2508,63 +2592,58 @@ did_set_string_option ( // If the new option is invalid, use old value. 'lisp' option: refill // g_chartab[] for '-' char if (init_chartab() == FAIL) { - did_chartab = TRUE; /* need to restore it below */ - errmsg = e_invarg; /* error in value */ + did_chartab = true; // need to restore it below + errmsg = e_invarg; // error in value } - } - /* 'helpfile' */ - else if (varp == &p_hf) { - /* May compute new values for $VIM and $VIMRUNTIME */ + } else if (varp == &p_hf) { // 'helpfile' + // May compute new values for $VIM and $VIMRUNTIME if (didset_vim) { vim_setenv("VIM", ""); - didset_vim = FALSE; + didset_vim = false; } if (didset_vimruntime) { vim_setenv("VIMRUNTIME", ""); - didset_vimruntime = FALSE; + didset_vimruntime = false; } - } - /* 'colorcolumn' */ - else if (varp == &curwin->w_p_cc) + } else if (varp == &curwin->w_p_cc) { // 'colorcolumn' errmsg = check_colorcolumn(curwin); - - /* 'helplang' */ - else if (varp == &p_hlg) { - /* Check for "", "ab", "ab,cd", etc. */ + } else if (varp == &p_hlg) { // 'helplang' + // Check for "", "ab", "ab,cd", etc. for (s = p_hlg; *s != NUL; s += 3) { if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { errmsg = e_invarg; break; } - if (s[2] == NUL) + if (s[2] == NUL) { break; + } } } else if (varp == &p_hl) { // 'highlight' if (strcmp((char *)(*varp), HIGHLIGHT_INIT) != 0) { errmsg = e_unsupportedoption; } - } - /* 'nrformats' */ - else if (gvarp == &p_nf) { - if (check_opt_strings(*varp, p_nf_values, TRUE) != OK) + } else if (gvarp == &p_nf) { // 'nrformats' + if (check_opt_strings(*varp, p_nf_values, true) != OK) { errmsg = e_invarg; + } } else if (varp == &p_ssop) { // 'sessionoptions' - if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) + if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { errmsg = e_invarg; + } if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { - /* Don't allow both "sesdir" and "curdir". */ + // Don't allow both "sesdir" and "curdir". (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); errmsg = e_invarg; } } else if (varp == &p_vop) { // 'viewoptions' - if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) + if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { errmsg = e_invarg; - } - /* 'scrollopt' */ - else if (varp == &p_sbo) { - if (check_opt_strings(p_sbo, p_scbopt_values, TRUE) != OK) + } + } else if (varp == &p_sbo) { // 'scrollopt' + if (check_opt_strings(p_sbo, p_scbopt_values, true) != OK) { errmsg = e_invarg; + } } else if (varp == &p_ambw || (int *)varp == &p_emoji) { // 'ambiwidth' if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { @@ -2583,13 +2662,11 @@ did_set_string_option ( ambw_end: {} // clint prefers {} over ; as an empty statement } - } - /* 'background' */ - else if (varp == &p_bg) { - if (check_opt_strings(p_bg, p_bg_values, FALSE) == OK) { + } else if (varp == &p_bg) { // 'background' + if (check_opt_strings(p_bg, p_bg_values, false) == OK) { int dark = (*p_bg == 'd'); - init_highlight(FALSE, FALSE); + init_highlight(false, false); if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { // The color scheme must have set 'background' back to another @@ -2599,31 +2676,28 @@ ambw_end: free_string_option(p_bg); p_bg = vim_strsave((char_u *)(dark ? "dark" : "light")); check_string_option(&p_bg); - init_highlight(FALSE, FALSE); + init_highlight(false, false); } } else errmsg = e_invarg; - } - /* 'wildmode' */ - else if (varp == &p_wim) { - if (check_opt_wim() == FAIL) + } else if (varp == &p_wim) { // 'wildmode' + if (check_opt_wim() == FAIL) { errmsg = e_invarg; - } - /* 'wildoptions' */ - else if (varp == &p_wop) { - if (check_opt_strings(p_wop, p_wop_values, TRUE) != OK) + } + // 'wildoptions' + } else if (varp == &p_wop) { + if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { errmsg = e_invarg; - } - /* 'winaltkeys' */ - else if (varp == &p_wak) { + } + } else if (varp == &p_wak) { // 'winaltkeys' if (*p_wak == NUL - || check_opt_strings(p_wak, p_wak_values, FALSE) != OK) + || check_opt_strings(p_wak, p_wak_values, false) != OK) { errmsg = e_invarg; - } - /* 'eventignore' */ - else if (varp == &p_ei) { - if (check_ei() == FAIL) + } + } else if (varp == &p_ei) { // 'eventignore' + if (check_ei() == FAIL) { errmsg = e_invarg; + } // 'encoding', 'fileencoding' and 'makeencoding' } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { if (gvarp == &p_fenc) { @@ -2642,7 +2716,7 @@ ambw_end: } if (errmsg == NULL) { - /* canonize the value, so that STRCMP() can be used on it */ + // canonize the value, so that STRCMP() can be used on it p = enc_canonize(*varp); xfree(*varp); *varp = p; @@ -2654,7 +2728,7 @@ ambw_end: } } } else if (varp == &p_penc) { - /* Canonize printencoding if VIM standard one */ + // Canonize printencoding if VIM standard one p = enc_canonize(p_penc); xfree(p_penc); p_penc = p; @@ -2662,22 +2736,37 @@ ambw_end: if (!valid_filetype(*varp)) { errmsg = e_invarg; } else { + int secure_save = secure; + + // Reset the secure flag, since the value of 'keymap' has + // been checked to be safe. + secure = 0; + // load or unload key mapping tables errmsg = keymap_init(); + + secure = secure_save; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; } if (errmsg == NULL) { if (*curbuf->b_p_keymap != NUL) { - /* Installed a new keymap, switch on using it. */ + // Installed a new keymap, switch on using it. curbuf->b_p_iminsert = B_IMODE_LMAP; - if (curbuf->b_p_imsearch != B_IMODE_USE_INSERT) + if (curbuf->b_p_imsearch != B_IMODE_USE_INSERT) { curbuf->b_p_imsearch = B_IMODE_LMAP; + } } else { - /* Cleared the keymap, may reset 'iminsert' and 'imsearch'. */ - if (curbuf->b_p_iminsert == B_IMODE_LMAP) + // Cleared the keymap, may reset 'iminsert' and 'imsearch'. + if (curbuf->b_p_iminsert == B_IMODE_LMAP) { curbuf->b_p_iminsert = B_IMODE_NONE; - if (curbuf->b_p_imsearch == B_IMODE_LMAP) + } + if (curbuf->b_p_imsearch == B_IMODE_LMAP) { curbuf->b_p_imsearch = B_IMODE_USE_INSERT; + } } if ((opt_flags & OPT_LOCAL) == 0) { set_iminsert_global(); @@ -2685,41 +2774,37 @@ ambw_end: } status_redraw_curbuf(); } - } - /* 'fileformat' */ - else if (gvarp == &p_ff) { - if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL)) + } else if (gvarp == &p_ff) { // 'fileformat' + if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL)) { errmsg = e_modifiable; - else if (check_opt_strings(*varp, p_ff_values, FALSE) != OK) + } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { errmsg = e_invarg; - else { + } else { redraw_titles(); - /* update flag in swap file */ + // update flag in swap file ml_setflags(curbuf); /* Redraw needed when switching to/from "mac": a CR in the text * will be displayed differently. */ - if (get_fileformat(curbuf) == EOL_MAC || *oldval == 'm') + if (get_fileformat(curbuf) == EOL_MAC || *oldval == 'm') { redraw_curbuf_later(NOT_VALID); + } } - } - /* 'fileformats' */ - else if (varp == &p_ffs) { - if (check_opt_strings(p_ffs, p_ff_values, TRUE) != OK) { + } else if (varp == &p_ffs) { // 'fileformats' + if (check_opt_strings(p_ffs, p_ff_values, true) != OK) { errmsg = e_invarg; } - } - - /* 'matchpairs' */ - else if (gvarp == &p_mps) { + } else if (gvarp == &p_mps) { // 'matchpairs' if (has_mbyte) { - for (p = *varp; *p != NUL; ++p) { + for (p = *varp; *p != NUL; p++) { int x2 = -1; int x3 = -1; - if (*p != NUL) + if (*p != NUL) { p += mb_ptr2len(p); - if (*p != NUL) + } + if (*p != NUL) { x2 = *p++; + } if (*p != NUL) { x3 = utf_ptr2char(p); p += mb_ptr2len(p); @@ -2728,42 +2813,45 @@ ambw_end: errmsg = e_invarg; break; } - if (*p == NUL) + if (*p == NUL) { break; + } } } else { - /* Check for "x:y,x:y" */ + // Check for "x:y,x:y" for (p = *varp; *p != NUL; p += 4) { if (p[1] != ':' || p[2] == NUL || (p[3] != NUL && p[3] != ',')) { errmsg = e_invarg; break; } - if (p[3] == NUL) + if (p[3] == NUL) { break; + } } } - } - /* 'comments' */ - else if (gvarp == &p_com) { + } else if (gvarp == &p_com) { // 'comments' for (s = *varp; *s; ) { while (*s && *s != ':') { if (vim_strchr((char_u *)COM_ALL, *s) == NULL && !ascii_isdigit(*s) && *s != '-') { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } - ++s; + s++; } - if (*s++ == NUL) + if (*s++ == NUL) { errmsg = (char_u *)N_("E524: Missing colon"); - else if (*s == ',' || *s == NUL) + } else if (*s == ',' || *s == NUL) { errmsg = (char_u *)N_("E525: Zero length string"); - if (errmsg != NULL) + } + if (errmsg != NULL) { break; + } while (*s && *s != ',') { - if (*s == '\\' && s[1] != NUL) - ++s; - ++s; + if (*s == '\\' && s[1] != NUL) { + s++; + } + s++; } s = skip_to_option_part(s); } @@ -2771,17 +2859,14 @@ ambw_end: errmsg = set_chars_option(curwin, varp); } else if (varp == &curwin->w_p_fcs) { // 'fillchars' errmsg = set_chars_option(curwin, varp); - } - /* 'cedit' */ - else if (varp == &p_cedit) { + } else if (varp == &p_cedit) { // 'cedit' errmsg = check_cedit(); - } - /* 'verbosefile' */ - else if (varp == &p_vfile) { + } else if (varp == &p_vfile) { // 'verbosefile' verbose_stop(); - if (*p_vfile != NUL && verbose_open() == FAIL) + if (*p_vfile != NUL && verbose_open() == FAIL) { errmsg = e_invarg; - /* 'shada' */ + } + // 'shada' } else if (varp == &p_shada) { // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo // option. @@ -2795,133 +2880,115 @@ ambw_end: // of the function and the set of P_ALLOCED at the end of the fuction. free_oldval = (options[opt_idx].flags & P_ALLOCED); for (s = p_shada; *s; ) { - /* Check it's a valid character */ + // Check it's a valid character if (vim_strchr((char_u *)"!\"%'/:<@cfhnrs", *s) == NULL) { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } - if (*s == 'n') { /* name is always last one */ + if (*s == 'n') { // name is always last one break; - } else if (*s == 'r') { /* skip until next ',' */ - while (*++s && *s != ',') - ; + } else if (*s == 'r') { // skip until next ',' + while (*++s && *s != ',') {} } else if (*s == '%') { - /* optional number */ - while (ascii_isdigit(*++s)) - ; - } else if (*s == '!' || *s == 'h' || *s == 'c') - ++s; /* no extra chars */ - else { /* must have a number */ - while (ascii_isdigit(*++s)) - ; + // optional number + while (ascii_isdigit(*++s)) {} + } else if (*s == '!' || *s == 'h' || *s == 'c') { + s++; // no extra chars + } else { // must have a number + while (ascii_isdigit(*++s)) {} if (!ascii_isdigit(*(s - 1))) { if (errbuf != NULL) { - sprintf((char *)errbuf, - _("E526: Missing number after <%s>"), - transchar_byte(*(s - 1))); + vim_snprintf((char *)errbuf, errbuflen, + _("E526: Missing number after <%s>"), + transchar_byte(*(s - 1))); errmsg = errbuf; } else errmsg = (char_u *)""; break; } } - if (*s == ',') - ++s; - else if (*s) { - if (errbuf != NULL) + if (*s == ',') { + s++; + } else if (*s) { + if (errbuf != NULL) { errmsg = (char_u *)N_("E527: Missing comma"); - else + } else { errmsg = (char_u *)""; + } break; } } - if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) + if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) { errmsg = (char_u *)N_("E528: Must specify a ' value"); - } - /* 'showbreak' */ - else if (varp == &p_sbr) { + } + } else if (varp == &p_sbr) { // 'showbreak' for (s = p_sbr; *s; ) { - if (ptr2cells(s) != 1) + if (ptr2cells(s) != 1) { errmsg = (char_u *)N_("E595: contains unprintable or wide character"); + } MB_PTR_ADV(s); } - } - - // 'guicursor' - else if (varp == &p_guicursor) { + } else if (varp == &p_guicursor) { // 'guicursor' errmsg = parse_shape_opt(SHAPE_CURSOR); - } - - else if (varp == &p_popt) + } else if (varp == &p_popt) { errmsg = parse_printoptions(); - else if (varp == &p_pmfn) + } else if (varp == &p_pmfn) { errmsg = parse_printmbfont(); - - /* 'langmap' */ - else if (varp == &p_langmap) + } else if (varp == &p_langmap) { // 'langmap' langmap_set(); - - /* 'breakat' */ - else if (varp == &p_breakat) + } else if (varp == &p_breakat) { // 'breakat' fill_breakat_flags(); - - /* 'titlestring' and 'iconstring' */ - else if (varp == &p_titlestring || varp == &p_iconstring) { + } else if (varp == &p_titlestring || varp == &p_iconstring) { + // 'titlestring' and 'iconstring' int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; - /* NULL => statusline syntax */ - if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) + // NULL => statusline syntax + if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { stl_syntax |= flagval; - else + } else { stl_syntax &= ~flagval; + } did_set_title(varp == &p_iconstring); - } - - /* 'selection' */ - else if (varp == &p_sel) { + } else if (varp == &p_sel) { // 'selection' if (*p_sel == NUL - || check_opt_strings(p_sel, p_sel_values, FALSE) != OK) + || check_opt_strings(p_sel, p_sel_values, false) != OK) { errmsg = e_invarg; - } - /* 'selectmode' */ - else if (varp == &p_slm) { - if (check_opt_strings(p_slm, p_slm_values, TRUE) != OK) + } + } else if (varp == &p_slm) { // 'selectmode' + if (check_opt_strings(p_slm, p_slm_values, true) != OK) { errmsg = e_invarg; - } - /* 'keymodel' */ - else if (varp == &p_km) { - if (check_opt_strings(p_km, p_km_values, TRUE) != OK) + } + } else if (varp == &p_km) { // 'keymodel' + if (check_opt_strings(p_km, p_km_values, true) != OK) { errmsg = e_invarg; - else { + } else { km_stopsel = (vim_strchr(p_km, 'o') != NULL); km_startsel = (vim_strchr(p_km, 'a') != NULL); } - } - /* 'mousemodel' */ - else if (varp == &p_mousem) { - if (check_opt_strings(p_mousem, p_mousem_values, FALSE) != OK) + } else if (varp == &p_mousem) { // 'mousemodel' + if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { errmsg = e_invarg; + } } else if (varp == &p_swb) { // 'switchbuf' - if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) + if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { errmsg = e_invarg; - } - /* 'debug' */ - else if (varp == &p_debug) { - if (check_opt_strings(p_debug, p_debug_values, TRUE) != OK) + } + } else if (varp == &p_debug) { // 'debug' + if (check_opt_strings(p_debug, p_debug_values, true) != OK) { errmsg = e_invarg; + } } else if (varp == &p_dy) { // 'display' - if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) + if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { errmsg = e_invarg; - else + } else { (void)init_chartab(); - - } - /* 'eadirection' */ - else if (varp == &p_ead) { - if (check_opt_strings(p_ead, p_ead_values, FALSE) != OK) + } + } else if (varp == &p_ead) { // 'eadirection' + if (check_opt_strings(p_ead, p_ead_values, false) != OK) { errmsg = e_invarg; + } } else if (varp == &p_cb) { // 'clipboard' if (opt_strings_flags(p_cb, p_cb_values, &cb_flags, true) != OK) { errmsg = e_invarg; @@ -2931,78 +2998,77 @@ ambw_end: // When 'spelllang' or 'spellfile' is set and there is a window for this // buffer in which 'spell' is set load the wordlists. errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf)); - } - /* When 'spellcapcheck' is set compile the regexp program. */ - else if (varp == &(curwin->w_s->b_p_spc)) { + } else if (varp == &(curwin->w_s->b_p_spc)) { + // When 'spellcapcheck' is set compile the regexp program. errmsg = compile_cap_prog(curwin->w_s); - } - /* 'spellsuggest' */ - else if (varp == &p_sps) { - if (spell_check_sps() != OK) + } else if (varp == &p_sps) { // 'spellsuggest' + if (spell_check_sps() != OK) { errmsg = e_invarg; - } - /* 'mkspellmem' */ - else if (varp == &p_msm) { - if (spell_check_msm() != OK) + } + } else if (varp == &p_msm) { // 'mkspellmem' + if (spell_check_msm() != OK) { errmsg = e_invarg; - } - /* When 'bufhidden' is set, check for valid value. */ - else if (gvarp == &p_bh) { - if (check_opt_strings(curbuf->b_p_bh, p_bufhidden_values, FALSE) != OK) + } + } else if (gvarp == &p_bh) { + // When 'bufhidden' is set, check for valid value. + if (check_opt_strings(curbuf->b_p_bh, p_bufhidden_values, false) != OK) { errmsg = e_invarg; - } - /* When 'buftype' is set, check for valid value. */ - else if (gvarp == &p_bt) { + } + } else if (gvarp == &p_bt) { + // When 'buftype' is set, check for valid value. if ((curbuf->terminal && curbuf->b_p_bt[0] != 't') || (!curbuf->terminal && curbuf->b_p_bt[0] == 't') - || check_opt_strings(curbuf->b_p_bt, p_buftype_values, FALSE) != OK) { + || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { errmsg = e_invarg; } else { if (curwin->w_status_height) { - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; redraw_later(VALID); } curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); redraw_titles(); } - } - /* 'statusline' or 'rulerformat' */ - else if (gvarp == &p_stl || varp == &p_ruf) { + } else if (gvarp == &p_stl || varp == &p_ruf) { + // 'statusline' or 'rulerformat' int wid; - if (varp == &p_ruf) /* reset ru_wid first */ + if (varp == &p_ruf) { // reset ru_wid first ru_wid = 0; + } s = *varp; if (varp == &p_ruf && *s == '%') { - /* set ru_wid if 'ruf' starts with "%99(" */ - if (*++s == '-') /* ignore a '-' */ + // set ru_wid if 'ruf' starts with "%99(" + if (*++s == '-') { // ignore a '-' s++; + } wid = getdigits_int(&s); - if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) + if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) { ru_wid = wid; - else + } else { errmsg = check_stl_option(p_ruf); - } - /* check 'statusline' only if it doesn't start with "%!" */ - else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') + } + } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { + // check 'statusline' only if it doesn't start with "%!" errmsg = check_stl_option(s); - if (varp == &p_ruf && errmsg == NULL) + } + if (varp == &p_ruf && errmsg == NULL) { comp_col(); - } - /* check if it is a valid value for 'complete' -- Acevedo */ - else if (gvarp == &p_cpt) { + } + } else if (gvarp == &p_cpt) { + // check if it is a valid value for 'complete' -- Acevedo for (s = *varp; *s; ) { while (*s == ',' || *s == ' ') s++; - if (!*s) + if (!*s) { break; + } if (vim_strchr((char_u *)".wbuksid]tU", *s) == NULL) { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } if (*++s != NUL && *s != ',' && *s != ' ') { if (s[-1] == 'k' || s[-1] == 's') { - /* skip optional filename after 'k' and 's' */ + // skip optional filename after 'k' and 's' while (*s && *s != ',' && *s != ' ') { if (*s == '\\' && s[1] != NUL) { s++; @@ -3011,9 +3077,9 @@ ambw_end: } } else { if (errbuf != NULL) { - sprintf((char *)errbuf, - _("E535: Illegal character after <%c>"), - *--s); + vim_snprintf((char *)errbuf, errbuflen, + _("E535: Illegal character after <%c>"), + *--s); errmsg = errbuf; } else errmsg = (char_u *)""; @@ -3021,9 +3087,7 @@ ambw_end: } } } - } - /* 'completeopt' */ - else if (varp == &p_cot) { + } else if (varp == &p_cot) { // 'completeopt' if (check_opt_strings(p_cot, p_cot_values, true) != OK) { errmsg = e_invarg; } else { @@ -3034,27 +3098,27 @@ ambw_end: if (check_opt_strings(*varp, p_scl_values, false) != OK) { errmsg = e_invarg; } - } - /* 'pastetoggle': translate key codes like in a mapping */ - else if (varp == &p_pt) { + } else if (varp == &p_pt) { + // 'pastetoggle': translate key codes like in a mapping if (*p_pt) { (void)replace_termcodes(p_pt, STRLEN(p_pt), &p, true, true, true, CPO_TO_CPO_FLAGS); if (p != NULL) { - if (new_value_alloced) + if (new_value_alloced) { free_string_option(p_pt); + } p_pt = p; - new_value_alloced = TRUE; + new_value_alloced = true; } } - } - /* 'backspace' */ - else if (varp == &p_bs) { + } else if (varp == &p_bs) { // 'backspace' if (ascii_isdigit(*p_bs)) { - if (*p_bs >'2' || p_bs[1] != NUL) + if (*p_bs >'2' || p_bs[1] != NUL) { errmsg = e_invarg; - } else if (check_opt_strings(p_bs, p_bs_values, TRUE) != OK) + } + } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { errmsg = e_invarg; + } } else if (varp == &p_bo) { if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { errmsg = e_invarg; @@ -3078,64 +3142,59 @@ ambw_end: errmsg = e_invarg; } } else if (varp == &p_cmp) { // 'casemap' - if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) + if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { errmsg = e_invarg; - } - /* 'diffopt' */ - else if (varp == &p_dip) { - if (diffopt_changed() == FAIL) + } + } else if (varp == &p_dip) { // 'diffopt' + if (diffopt_changed() == FAIL) { errmsg = e_invarg; - } - /* 'foldmethod' */ - else if (gvarp == &curwin->w_allbuf_opt.wo_fdm) { - if (check_opt_strings(*varp, p_fdm_values, FALSE) != OK - || *curwin->w_p_fdm == NUL) + } + } else if (gvarp == &curwin->w_allbuf_opt.wo_fdm) { // 'foldmethod' + if (check_opt_strings(*varp, p_fdm_values, false) != OK + || *curwin->w_p_fdm == NUL) { errmsg = e_invarg; - else { + } else { foldUpdateAll(curwin); - if (foldmethodIsDiff(curwin)) + if (foldmethodIsDiff(curwin)) { newFoldLevel(); + } } - } - /* 'foldexpr' */ - else if (varp == &curwin->w_p_fde) { - if (foldmethodIsExpr(curwin)) + } else if (varp == &curwin->w_p_fde) { // 'foldexpr' + if (foldmethodIsExpr(curwin)) { foldUpdateAll(curwin); - } - /* 'foldmarker' */ - else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { + } + } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' p = vim_strchr(*varp, ','); - if (p == NULL) + if (p == NULL) { errmsg = (char_u *)N_("E536: comma required"); - else if (p == *varp || p[1] == NUL) + } else if (p == *varp || p[1] == NUL) { errmsg = e_invarg; - else if (foldmethodIsMarker(curwin)) + } else if (foldmethodIsMarker(curwin)) { foldUpdateAll(curwin); - } - /* 'commentstring' */ - else if (gvarp == &p_cms) { - if (**varp != NUL && strstr((char *)*varp, "%s") == NULL) + } + } else if (gvarp == &p_cms) { // 'commentstring' + if (**varp != NUL && strstr((char *)(*varp), "%s") == NULL) { errmsg = (char_u *)N_( - "E537: 'commentstring' must be empty or contain %s"); + "E537: 'commentstring' must be empty or contain %s"); + } } else if (varp == &p_fdo) { // 'foldopen' - if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) + if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { errmsg = e_invarg; - } - /* 'foldclose' */ - else if (varp == &p_fcl) { - if (check_opt_strings(p_fcl, p_fcl_values, TRUE) != OK) + } + } else if (varp == &p_fcl) { // 'foldclose' + if (check_opt_strings(p_fcl, p_fcl_values, true) != OK) { errmsg = e_invarg; - } - /* 'foldignore' */ - else if (gvarp == &curwin->w_allbuf_opt.wo_fdi) { - if (foldmethodIsIndent(curwin)) + } + } else if (gvarp == &curwin->w_allbuf_opt.wo_fdi) { // 'foldignore' + if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); + } } else if (varp == &p_ve) { // 'virtualedit' - if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) + if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) { errmsg = e_invarg; - else if (STRCMP(p_ve, oldval) != 0) { - /* Recompute cursor position in case the new 've' setting - * changes something. */ + } else if (STRCMP(p_ve, oldval) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. validate_virtcol(); coladvance(curwin->w_virtcol); } @@ -3149,16 +3208,15 @@ ambw_end: || (p[2] != NUL && p[2] != ',')) { errmsg = e_invarg; break; - } else if (p[2] == NUL) + } else if (p[2] == NUL) { break; - else + } else { p += 3; + } } } - } - /* 'cinoptions' */ - else if (gvarp == &p_cino) { - /* TODO: recognize errors */ + } else if (gvarp == &p_cino) { // 'cinoptions' + // TODO(vim): recognize errors parse_cino(curbuf); // inccommand } else if (varp == &p_icm) { @@ -3170,12 +3228,20 @@ ambw_end: errmsg = e_invarg; } else { value_changed = STRCMP(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; } } else if (gvarp == &p_syn) { if (!valid_filetype(*varp)) { errmsg = e_invarg; } else { value_changed = STRCMP(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; } } else if (varp == &curwin->w_p_winhl) { if (!parse_winhl_opt(curwin)) { @@ -3199,11 +3265,12 @@ ambw_end: p = (char_u *)MOUSE_ALL; } if (p != NULL) { - for (s = *varp; *s; ++s) + for (s = *varp; *s; s++) { if (vim_strchr(p, *s) == NULL) { - errmsg = illegal_char(errbuf, *s); + errmsg = illegal_char(errbuf, errbuflen, *s); break; } + } } } @@ -3211,28 +3278,32 @@ ambw_end: * If error detected, restore the previous value. */ if (errmsg != NULL) { - if (new_value_alloced) + if (new_value_alloced) { free_string_option(*varp); + } *varp = oldval; /* * When resetting some values, need to act on it. */ - if (did_chartab) + if (did_chartab) { (void)init_chartab(); + } } else { - /* Remember where the option was set. */ + // Remember where the option was set. set_option_scriptID_idx(opt_idx, opt_flags, current_SID); /* * Free string options that are in allocated memory. * Use "free_oldval", because recursiveness may change the flags under * our fingers (esp. init_highlight()). */ - if (free_oldval) + if (free_oldval) { free_string_option(oldval); - if (new_value_alloced) + } + if (new_value_alloced) { options[opt_idx].flags |= P_ALLOCED; - else + } else { options[opt_idx].flags &= ~P_ALLOCED; + } if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && ((int)options[opt_idx].indir & PV_BOTH)) { @@ -3241,15 +3312,15 @@ ambw_end: p = get_varp_scope(&(options[opt_idx]), OPT_LOCAL); free_string_option(*(char_u **)p); *(char_u **)p = empty_option; - } - /* May set global value for local option. */ - else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) + } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { + // May set global value for local option. set_string_option_global(opt_idx, varp); + } /* * Trigger the autocommand only after setting the flags. */ - /* When 'syntax' is set, load the syntax of that name */ + // When 'syntax' is set, load the syntax of that name if (varp == &(curbuf->b_p_syn)) { static int syn_recursive = 0; @@ -3265,6 +3336,11 @@ ambw_end: // already set to this value. if (!(opt_flags & OPT_MODELINE) || value_changed) { static int ft_recursive = 0; + int secure_save = secure; + + // Reset the secure flag, since the value of 'filetype' has + // been checked to be safe. + secure = 0; ft_recursive++; did_filetype = true; @@ -3277,15 +3353,17 @@ ambw_end: if (varp != &(curbuf->b_p_ft)) { varp = NULL; } + secure = secure_save; } } if (varp == &(curwin->w_s->b_p_spl)) { char_u fname[200]; char_u *q = curwin->w_s->b_p_spl; - /* Skip the first name if it is "cjk". */ - if (STRNCMP(q, "cjk,", 4) == 0) + // Skip the first name if it is "cjk". + if (STRNCMP(q, "cjk,", 4) == 0) { q += 4; + } /* * Source the spell/LANG.vim in 'runtimepath'. @@ -3293,12 +3371,16 @@ ambw_end: * Use the first name in 'spelllang' up to '_region' or * '.encoding'. */ - for (p = q; *p != NUL; ++p) - if (vim_strchr((char_u *)"_.,", *p) != NULL) + for (p = q; *p != NUL; p++) { + if (!ASCII_ISALNUM(*p) && *p != '-') { break; - vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - q), q); - source_runtime(fname, DIP_ALL); + } + } + if (p > q) { + vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", + (int)(p - q), q); + source_runtime(fname, DIP_ALL); + } } } @@ -3312,28 +3394,22 @@ ambw_end: if (curwin->w_curswant != MAXCOL && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; check_redraw(options[opt_idx].flags); return errmsg; -} -#ifdef _MSC_VER -#pragma optimize("", on) -#endif +} // NOLINT(readability/fn_size) -/* - * Simple int comparison function for use with qsort() - */ +/// Simple int comparison function for use with qsort() static int int_cmp(const void *a, const void *b) { return *(const int *)a - *(const int *)b; } -/* - * Handle setting 'colorcolumn' or 'textwidth' in window "wp". - * Returns error message, NULL if it's OK. - */ +/// Handle setting 'colorcolumn' or 'textwidth' in window "wp". +/// +/// @return error message, NULL if it's OK. char_u *check_colorcolumn(win_T *wp) { char_u *s; @@ -3342,19 +3418,22 @@ char_u *check_colorcolumn(win_T *wp) int color_cols[256]; int j = 0; - if (wp->w_buffer == NULL) - return NULL; /* buffer was closed */ + if (wp->w_buffer == NULL) { + return NULL; // buffer was closed + } for (s = wp->w_p_cc; *s != NUL && count < 255; ) { if (*s == '-' || *s == '+') { - /* -N and +N: add to 'textwidth' */ + // -N and +N: add to 'textwidth' col = (*s == '-') ? -1 : 1; - ++s; - if (!ascii_isdigit(*s)) + s++; + if (!ascii_isdigit(*s)) { return e_invarg; + } col = col * getdigits_int(&s); - if (wp->w_buffer->b_p_tw == 0) - goto skip; /* 'textwidth' not set, skip this item */ + if (wp->w_buffer->b_p_tw == 0) { + goto skip; // 'textwidth' not set, skip this item + } assert((col >= 0 && wp->w_buffer->b_p_tw <= INT_MAX - col && wp->w_buffer->b_p_tw + col >= INT_MIN) @@ -3362,39 +3441,46 @@ char_u *check_colorcolumn(win_T *wp) && wp->w_buffer->b_p_tw >= INT_MIN - col && wp->w_buffer->b_p_tw + col <= INT_MAX)); col += (int)wp->w_buffer->b_p_tw; - if (col < 0) + if (col < 0) { goto skip; - } else if (ascii_isdigit(*s)) + } + } else if (ascii_isdigit(*s)) { col = getdigits_int(&s); - else + } else { return e_invarg; - color_cols[count++] = col - 1; /* 1-based to 0-based */ + } + color_cols[count++] = col - 1; // 1-based to 0-based skip: - if (*s == NUL) + if (*s == NUL) { break; - if (*s != ',') + } + if (*s != ',') { return e_invarg; - if (*++s == NUL) - return e_invarg; /* illegal trailing comma as in "set cc=80," */ + } + if (*++s == NUL) { + return e_invarg; // illegal trailing comma as in "set cc=80," + } } xfree(wp->w_p_cc_cols); - if (count == 0) + if (count == 0) { wp->w_p_cc_cols = NULL; - else { + } else { wp->w_p_cc_cols = xmalloc(sizeof(int) * (count + 1)); /* sort the columns for faster usage on screen redraw inside * win_line() */ qsort(color_cols, count, sizeof(int), int_cmp); - for (unsigned int i = 0; i < count; ++i) - /* skip duplicates */ - if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) + for (unsigned int i = 0; i < count; i++) { + // skip duplicates + if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { wp->w_p_cc_cols[j++] = color_cols[i]; - wp->w_p_cc_cols[j] = -1; /* end marker */ + } + } + wp->w_p_cc_cols[j] = -1; // end marker } - return NULL; /* no error */ + return NULL; // no error } @@ -3469,7 +3555,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) } p = *varp; while (*p) { - for (i = 0; i < entries; ++i) { + for (i = 0; i < entries; i++) { len = (int)STRLEN(tab[i].name); if (STRNCMP(p, tab[i].name, len) == 0 && p[len] == ':' @@ -3516,14 +3602,16 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) } } - if (i == entries) + if (i == entries) { return e_invarg; - if (*p == ',') - ++p; + } + if (*p == ',') { + p++; + } } } - return NULL; /* no error */ + return NULL; // no error } /* @@ -3537,11 +3625,13 @@ char_u *check_stl_option(char_u *s) static char_u errbuf[80]; while (*s && itemcnt < STL_MAX_ITEM) { - /* Check for valid keys after % sequences */ - while (*s && *s != '%') + // Check for valid keys after % sequences + while (*s && *s != '%') { s++; - if (!*s) + } + if (!*s) { break; + } s++; if (*s != '%' && *s != ')') { itemcnt++; @@ -3552,16 +3642,20 @@ char_u *check_stl_option(char_u *s) } if (*s == ')') { s++; - if (--groupdepth < 0) + if (--groupdepth < 0) { break; + } continue; } - if (*s == '-') + if (*s == '-') { s++; - while (ascii_isdigit(*s)) + } + while (ascii_isdigit(*s)) { s++; - if (*s == STL_USER_HL) + } + if (*s == STL_USER_HL) { continue; + } if (*s == '.') { s++; while (*s && ascii_isdigit(*s)) @@ -3572,20 +3666,23 @@ char_u *check_stl_option(char_u *s) continue; } if (vim_strchr(STL_ALL, *s) == NULL) { - return illegal_char(errbuf, *s); + return illegal_char(errbuf, sizeof(errbuf), *s); } if (*s == '{') { s++; while (*s != '}' && *s) s++; - if (*s != '}') + if (*s != '}') { return (char_u *)N_("E540: Unclosed expression sequence"); + } } } - if (itemcnt >= STL_MAX_ITEM) + if (itemcnt >= STL_MAX_ITEM) { return (char_u *)N_("E541: too many items"); - if (groupdepth != 0) + } + if (groupdepth != 0) { return (char_u *)N_("E542: unbalanced groups"); + } return NULL; } @@ -3622,15 +3719,15 @@ static char_u *compile_cap_prog(synblock_T *synblock) regprog_T *rp = synblock->b_cap_prog; char_u *re; - if (*synblock->b_p_spc == NUL) + if (*synblock->b_p_spc == NUL) { synblock->b_cap_prog = NULL; - else { - /* Prepend a ^ so that we only match at one column */ + } else { + // Prepend a ^ so that we only match at one column re = concat_str((char_u *)"^", synblock->b_p_spc); synblock->b_cap_prog = vim_regcomp(re, RE_MAGIC); xfree(re); if (synblock->b_cap_prog == NULL) { - synblock->b_cap_prog = rp; /* restore the previous program */ + synblock->b_cap_prog = rp; // restore the previous program return e_invarg; } } @@ -3718,20 +3815,21 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, { int old_value = *(int *)varp; - /* Disallow changing some options from secure mode */ + // Disallow changing some options from secure mode if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { return (char *)e_secure; } - *(int *)varp = value; /* set the new value */ - /* Remember where the option was set. */ + *(int *)varp = value; // set the new value + // Remember where the option was set. set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = value; + } // Ensure that options set to p_force_on cannot be disabled. if ((int *)varp == &p_force_on && p_force_on == false) { @@ -3775,72 +3873,65 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, curbuf = save_curbuf; } } else if ((int *)varp == &curbuf->b_p_ro) { - /* when 'readonly' is reset globally, also reset readonlymode */ - if (!curbuf->b_p_ro && (opt_flags & OPT_LOCAL) == 0) - readonlymode = FALSE; + // when 'readonly' is reset globally, also reset readonlymode + if (!curbuf->b_p_ro && (opt_flags & OPT_LOCAL) == 0) { + readonlymode = false; + } - /* when 'readonly' is set may give W10 again */ - if (curbuf->b_p_ro) + // when 'readonly' is set may give W10 again + if (curbuf->b_p_ro) { curbuf->b_did_warn = false; + } redraw_titles(); - } - /* when 'modifiable' is changed, redraw the window title */ - else if ((int *)varp == &curbuf->b_p_ma) { + } else if ((int *)varp == &curbuf->b_p_ma) { + // when 'modifiable' is changed, redraw the window title redraw_titles(); - } - /* when 'endofline' is changed, redraw the window title */ - else if ((int *)varp == &curbuf->b_p_eol) { + } else if ((int *)varp == &curbuf->b_p_eol) { + // when 'endofline' is changed, redraw the window title redraw_titles(); } else if ((int *)varp == &curbuf->b_p_fixeol) { // when 'fixeol' is changed, redraw the window title redraw_titles(); - } - /* when 'bomb' is changed, redraw the window title and tab page text */ - else if ((int *)varp == &curbuf->b_p_bomb) { + } else if ((int *)varp == &curbuf->b_p_bomb) { + // when 'bomb' is changed, redraw the window title and tab page text redraw_titles(); - } - /* when 'bin' is set also set some other options */ - else if ((int *)varp == &curbuf->b_p_bin) { + } else if ((int *)varp == &curbuf->b_p_bin) { + // when 'bin' is set also set some other options set_options_bin(old_value, curbuf->b_p_bin, opt_flags); redraw_titles(); - } - /* when 'buflisted' changes, trigger autocommands */ - else if ((int *)varp == &curbuf->b_p_bl && old_value != curbuf->b_p_bl) { + } else if ((int *)varp == &curbuf->b_p_bl && old_value != curbuf->b_p_bl) { + // when 'buflisted' changes, trigger autocommands apply_autocmds(curbuf->b_p_bl ? EVENT_BUFADD : EVENT_BUFDELETE, - NULL, NULL, TRUE, curbuf); - } - /* when 'swf' is set, create swapfile, when reset remove swapfile */ - else if ((int *)varp == (int *)&curbuf->b_p_swf) { - if (curbuf->b_p_swf && p_uc) - ml_open_file(curbuf); /* create the swap file */ - else - /* no need to reset curbuf->b_may_swap, ml_open_file() will check - * buf->b_p_swf */ - mf_close_file(curbuf, true); /* remove the swap file */ - } - /* when 'terse' is set change 'shortmess' */ - else if ((int *)varp == &p_terse) { + NULL, NULL, true, curbuf); + } else if ((int *)varp == (int *)&curbuf->b_p_swf) { + // when 'swf' is set, create swapfile, when reset remove swapfile + if (curbuf->b_p_swf && p_uc) { + ml_open_file(curbuf); // create the swap file + } else { + // no need to reset curbuf->b_may_swap, ml_open_file() will check + // buf->b_p_swf + mf_close_file(curbuf, true); // remove the swap file + } + } else if ((int *)varp == &p_terse) { + // when 'terse' is set change 'shortmess' char_u *p; p = vim_strchr(p_shm, SHM_SEARCH); - /* insert 's' in p_shm */ + // insert 's' in p_shm if (p_terse && p == NULL) { STRCPY(IObuff, p_shm); STRCAT(IObuff, "s"); set_string_option_direct((char_u *)"shm", -1, IObuff, OPT_FREE, 0); - } - /* remove 's' from p_shm */ - else if (!p_terse && p != NULL) + } else if (!p_terse && p != NULL) { // remove 's' from p_shm STRMOVE(p, p + 1); - } - /* when 'paste' is set or reset also change other options */ - else if ((int *)varp == &p_paste) { + } + } else if ((int *)varp == &p_paste) { + // when 'paste' is set or reset also change other options paste_option_changed(); - } - /* when 'insertmode' is set from an autocommand need to do work here */ - else if ((int *)varp == &p_im) { + } else if ((int *)varp == &p_im) { + // when 'insertmode' is set from an autocommand need to do work here if (p_im) { if ((State & INSERT) == 0) { need_start_insertmode = true; @@ -3854,25 +3945,21 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, } restart_edit = 0; } - } - /* when 'ignorecase' is set or reset and 'hlsearch' is set, redraw */ - else if ((int *)varp == &p_ic && p_hls) { + } else if ((int *)varp == &p_ic && p_hls) { + // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw redraw_all_later(SOME_VALID); - } - /* when 'hlsearch' is set or reset: reset no_hlsearch */ - else if ((int *)varp == &p_hls) { - SET_NO_HLSEARCH(FALSE); - } - /* when 'scrollbind' is set: snapshot the current position to avoid a jump - * at the end of normal_cmd() */ - else if ((int *)varp == &curwin->w_p_scb) { + } else if ((int *)varp == &p_hls) { + // when 'hlsearch' is set or reset: reset no_hlsearch + SET_NO_HLSEARCH(false); + } else if ((int *)varp == &curwin->w_p_scb) { + // when 'scrollbind' is set: snapshot the current position to avoid a jump + // at the end of normal_cmd() if (curwin->w_p_scb) { - do_check_scrollbind(FALSE); + do_check_scrollbind(false); curwin->w_scbind_pos = curwin->w_topline; } - } - /* There can be only one window with 'previewwindow' set. */ - else if ((int *)varp == &curwin->w_p_pvw) { + } else if ((int *)varp == &curwin->w_p_pvw) { + // There can be only one window with 'previewwindow' set. if (curwin->w_p_pvw) { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (win->w_p_pvw && win != curwin) { @@ -3910,17 +3997,17 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, pseps[0] = '\\'; } - /* need to adjust the file name arguments and buffer names. */ + // need to adjust the file name arguments and buffer names. buflist_slash_adjust(); alist_slash_adjust(); scriptnames_slash_adjust(); } #endif - - /* If 'wrap' is set, set w_leftcol to zero. */ else if ((int *)varp == &curwin->w_p_wrap) { - if (curwin->w_p_wrap) + // If 'wrap' is set, set w_leftcol to zero. + if (curwin->w_p_wrap) { curwin->w_leftcol = 0; + } } else if ((int *)varp == &p_ea) { if (p_ea && !old_value) { win_equal(curwin, false, 0); @@ -3928,22 +4015,18 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, } else if ((int *)varp == &p_acd) { // Change directories when the 'acd' option is set now. do_autochdir(); - } - /* 'diff' */ - else if ((int *)varp == &curwin->w_p_diff) { - /* May add or remove the buffer from the list of diff buffers. */ + } else if ((int *)varp == &curwin->w_p_diff) { // 'diff' + // May add or remove the buffer from the list of diff buffers. diff_buf_adjust(curwin); - if (foldmethodIsDiff(curwin)) + if (foldmethodIsDiff(curwin)) { foldUpdateAll(curwin); - } - - - /* 'spell' */ - else if ((int *)varp == &curwin->w_p_spell) { + } + } else if ((int *)varp == &curwin->w_p_spell) { // 'spell' if (curwin->w_p_spell) { char_u *errmsg = did_set_spelllang(curwin); - if (errmsg != NULL) + if (errmsg != NULL) { EMSG(_(errmsg)); + } } } @@ -3953,13 +4036,13 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, * 'arabic' is set, handle various sub-settings. */ if (!p_tbidi) { - /* set rightleft mode */ + // set rightleft mode if (!curwin->w_p_rl) { - curwin->w_p_rl = TRUE; + curwin->w_p_rl = true; changed_window_setting(); } - /* Enable Arabic shaping (major part of what Arabic requires) */ + // Enable Arabic shaping (major part of what Arabic requires) if (!p_arshape) { p_arshape = true; redraw_all_later(NOT_VALID); @@ -3977,8 +4060,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1); } - /* set 'delcombine' */ - p_deco = TRUE; + // set 'delcombine' + p_deco = true; // Force-set the necessary keymap for arabic. set_option_value("keymap", 0L, "arabic", OPT_LOCAL); @@ -3987,9 +4070,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, * 'arabic' is reset, handle various sub-settings. */ if (!p_tbidi) { - /* reset rightleft mode */ + // reset rightleft mode if (curwin->w_p_rl) { - curwin->w_p_rl = FALSE; + curwin->w_p_rl = false; changed_window_setting(); } @@ -4000,7 +4083,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, /* 'delcombine' isn't reset, it is a global option and another * window may still want it "on". */ - /* Revert to the default keymap */ + // Revert to the default keymap curbuf->b_p_iminsert = B_IMODE_NONE; curbuf->b_p_imsearch = B_IMODE_USE_INSERT; } @@ -4040,10 +4123,11 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, BOOLEAN_OBJ(value)); } - comp_col(); /* in case 'ruler' or 'showcmd' changed */ + comp_col(); // in case 'ruler' or 'showcmd' changed if (curwin->w_curswant != MAXCOL - && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) - curwin->w_set_curswant = TRUE; + && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) { + curwin->w_set_curswant = true; + } check_redraw(options[opt_idx].flags); return NULL; @@ -4064,11 +4148,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, { char_u *errmsg = NULL; long old_value = *(long *)varp; - long old_Rows = Rows; /* remember old Rows */ - long old_Columns = Columns; /* remember old Columns */ + long old_Rows = Rows; // remember old Rows + long old_Columns = Columns; // remember old Columns long *pp = (long *)varp; - /* Disallow changing some options from secure mode. */ + // Disallow changing some options from secure mode. if ((secure || sandbox != 0) && (options[opt_idx].flags & P_SECURE)) { return (char *)e_secure; @@ -4374,21 +4458,21 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, && curwin->w_height > 0)) && full_screen) { if (pp == &(curwin->w_p_scr)) { - if (curwin->w_p_scr != 0) + if (curwin->w_p_scr != 0) { errmsg = e_scroll; + } win_comp_scroll(curwin); - } - /* If 'scroll' became invalid because of a side effect silently adjust - * it. */ - else if (curwin->w_p_scr <= 0) + } else if (curwin->w_p_scr <= 0) { + // If 'scroll' became invalid because of a side effect silently adjust it. curwin->w_p_scr = 1; - else /* curwin->w_p_scr > curwin->w_height */ + } else { // curwin->w_p_scr > curwin->w_height curwin->w_p_scr = curwin->w_height; + } } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { - if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ + if (Rows != old_Rows) { // Rows changed, just adjust p_sj p_sj = Rows / 2; - else { + } else { errmsg = e_scroll; p_sj = 1; } @@ -4424,10 +4508,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, INTEGER_OBJ(value)); } - comp_col(); /* in case 'columns' or 'ls' changed */ + comp_col(); // in case 'columns' or 'ls' changed if (curwin->w_curswant != MAXCOL - && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) - curwin->w_set_curswant = TRUE; + && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) { + curwin->w_set_curswant = true; + } check_redraw(options[opt_idx].flags); return (char *)errmsg; @@ -4458,7 +4543,7 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags, */ static void check_redraw(uint32_t flags) { - /* Careful: P_RCLR and P_RALL are a combination of other P_ flags */ + // Careful: P_RCLR and P_RALL are a combination of other P_ flags bool doclear = (flags & P_RCLR) == P_RCLR; bool all = ((flags & P_RALL) == P_RALL || doclear); @@ -4691,7 +4776,7 @@ int get_option_value( // opt_type). // // Returned flags: -// 0 hidden or unknown option, also option that does not have requested +// 0 hidden or unknown option, also option that does not have requested // type (see SREQ_* in option_defs.h) // see SOPT_* in option_defs.h for other flags // @@ -4845,7 +4930,7 @@ char *set_option_value(const char *const name, const long number, return set_string_option(opt_idx, s, opt_flags); } else { varp = get_varp_scope(&(options[opt_idx]), opt_flags); - if (varp != NULL) { /* hidden option is not changed */ + if (varp != NULL) { // hidden option is not changed if (number == 0 && string != NULL) { int idx; @@ -4904,10 +4989,10 @@ static int find_key_option(const char_u *arg) * if 'all' == 0: show changed options * if 'all' == 1: show all normal options */ -static void -showoptions ( +static void +showoptions( int all, - int opt_flags /* OPT_LOCAL and/or OPT_GLOBAL */ + int opt_flags // OPT_LOCAL and/or OPT_GLOBAL ) { vimoption_T *p; @@ -4934,28 +5019,26 @@ showoptions ( MSG_PUTS_TITLE(_("\n--- Options ---")); } - /* - * do the loop two times: - * 1. display the short items - * 2. display the long items (only strings and numbers) - */ - for (run = 1; run <= 2 && !got_int; ++run) { - /* - * collect the items in items[] - */ + // Do the loop two times: + // 1. display the short items + // 2. display the long items (only strings and numbers) + for (run = 1; run <= 2 && !got_int; run++) { + // collect the items in items[] item_count = 0; for (p = &options[0]; p->fullname != NULL; p++) { varp = NULL; if (opt_flags != 0) { - if (p->indir != PV_NONE) + if (p->indir != PV_NONE) { varp = get_varp_scope(p, opt_flags); - } else + } + } else { varp = get_varp(p); + } if (varp != NULL && (all == 1 || (all == 0 && !optval_default(p, varp)))) { - if (p->flags & P_BOOL) - len = 1; /* a toggle option fits always */ - else { + if (p->flags & P_BOOL) { + len = 1; // a toggle option fits always + } else { option_value2string(p, opt_flags); len = (int)STRLEN(p->fullname) + vim_strsize(NameBuff) + 1; } @@ -4975,18 +5058,21 @@ showoptions ( && (Columns + GAP - 3) / INC >= INT_MIN && (Columns + GAP - 3) / INC <= INT_MAX); cols = (int)((Columns + GAP - 3) / INC); - if (cols == 0) + if (cols == 0) { cols = 1; + } rows = (item_count + cols - 1) / cols; - } else /* run == 2 */ + } else { // run == 2 rows = item_count; - for (row = 0; row < rows && !got_int; ++row) { - msg_putchar('\n'); /* go to next line */ - if (got_int) /* 'q' typed in more */ + } + for (row = 0; row < rows && !got_int; row++) { + msg_putchar('\n'); // go to next line + if (got_int) { // 'q' typed in more break; + } col = 0; for (i = row; i < item_count; i += rows) { - msg_col = col; /* make columns */ + msg_col = col; // make columns showoneopt(items[i], opt_flags); col += INC; } @@ -4997,15 +5083,14 @@ showoptions ( xfree(items); } -/* - * Return TRUE if option "p" has its default value. - */ +/// Return true if option "p" has its default value. static int optval_default(vimoption_T *p, char_u *varp) { int dvi; - if (varp == NULL) - return TRUE; /* hidden option is always at default */ + if (varp == NULL) { + return true; // hidden option is always at default + } dvi = ((p->flags & P_VI_DEF) || p_cp) ? VI_DEFAULT : VIM_DEFAULT; if (p->flags & P_NUM) { return *(long *)varp == (long)(intptr_t)p->def_val[dvi]; @@ -5044,17 +5129,17 @@ void ui_refresh_options(void) * showoneopt: show the value of one option * must not be called with a hidden option! */ -static void -showoneopt ( +static void +showoneopt( vimoption_T *p, - int opt_flags /* OPT_LOCAL or OPT_GLOBAL */ + int opt_flags // OPT_LOCAL or OPT_GLOBAL ) { char_u *varp; int save_silent = silent_mode; - silent_mode = FALSE; - info_message = TRUE; /* use mch_msg(), not mch_errmsg() */ + silent_mode = false; + info_message = true; // use mch_msg(), not mch_errmsg() varp = get_varp_scope(p, opt_flags); @@ -5070,13 +5155,13 @@ showoneopt ( MSG_PUTS(p->fullname); if (!(p->flags & P_BOOL)) { msg_putchar('='); - /* put value string in NameBuff */ + // put value string in NameBuff option_value2string(p, opt_flags); msg_outtrans(NameBuff); } silent_mode = save_silent; - info_message = FALSE; + info_message = false; } /* @@ -5095,7 +5180,7 @@ showoneopt ( * may have set them when doing ":edit file" and the * user has set them back at the default or fresh * value. - * When "local_only" is TRUE, don't write fresh + * When "local_only" is true, don't write fresh * values, only local values (for ":mkview"). * (fresh value = value used for a new buffer or window for a local option). * @@ -5104,9 +5189,9 @@ showoneopt ( int makeset(FILE *fd, int opt_flags, int local_only) { vimoption_T *p; - char_u *varp; /* currently used value */ - char_u *varp_fresh; /* local value */ - char_u *varp_local = NULL; /* fresh value */ + char_u *varp; // currently used value + char_u *varp_fresh; // local value + char_u *varp_local = NULL; // fresh value char *cmd; int round; int pri; @@ -5120,35 +5205,40 @@ int makeset(FILE *fd, int opt_flags, int local_only) * Do the loop over "options[]" twice: once for options with the * P_PRI_MKRC flag and once without. */ - for (pri = 1; pri >= 0; --pri) { - for (p = &options[0]; p->fullname; p++) + for (pri = 1; pri >= 0; pri--) { + for (p = &options[0]; p->fullname; p++) { if (!(p->flags & P_NO_MKRC) && ((pri == 1) == ((p->flags & P_PRI_MKRC) != 0))) { - /* skip global option when only doing locals */ - if (p->indir == PV_NONE && !(opt_flags & OPT_GLOBAL)) + // skip global option when only doing locals + if (p->indir == PV_NONE && !(opt_flags & OPT_GLOBAL)) { continue; + } /* Do not store options like 'bufhidden' and 'syntax' in a vimrc * file, they are always buffer-specific. */ - if ((opt_flags & OPT_GLOBAL) && (p->flags & P_NOGLOB)) + if ((opt_flags & OPT_GLOBAL) && (p->flags & P_NOGLOB)) { continue; + } varp = get_varp_scope(p, opt_flags); - /* Hidden options are never written. */ - if (!varp) + // Hidden options are never written. + if (!varp) { continue; - /* Global values are only written when not at the default value. */ - if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) + } + // Global values are only written when not at the default value. + if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) { continue; + } round = 2; if (p->indir != PV_NONE) { if (p->var == VAR_WIN) { - /* skip window-local option when only doing globals */ - if (!(opt_flags & OPT_LOCAL)) + // skip window-local option when only doing globals + if (!(opt_flags & OPT_LOCAL)) { continue; - /* When fresh value of window-local option is not at the - * default, need to write it too. */ + } + // When fresh value of window-local option is not at the + // default, need to write it too. if (!(opt_flags & OPT_GLOBAL) && !local_only) { varp_fresh = get_varp_scope(p, OPT_GLOBAL); if (!optval_default(p, varp_fresh)) { @@ -5162,20 +5252,23 @@ int makeset(FILE *fd, int opt_flags, int local_only) /* Round 1: fresh value for window-local options. * Round 2: other values */ - for (; round <= 2; varp = varp_local, ++round) { - if (round == 1 || (opt_flags & OPT_GLOBAL)) + for (; round <= 2; varp = varp_local, round++) { + if (round == 1 || (opt_flags & OPT_GLOBAL)) { cmd = "set"; - else + } else { cmd = "setlocal"; + } if (p->flags & P_BOOL) { - if (put_setbool(fd, cmd, p->fullname, *(int *)varp) == FAIL) + if (put_setbool(fd, cmd, p->fullname, *(int *)varp) == FAIL) { return FAIL; + } } else if (p->flags & P_NUM) { - if (put_setnum(fd, cmd, p->fullname, (long *)varp) == FAIL) + if (put_setnum(fd, cmd, p->fullname, (long *)varp) == FAIL) { return FAIL; - } else { /* P_STRING */ - int do_endif = FALSE; + } + } else { // P_STRING + int do_endif = false; // Don't set 'syntax' and 'filetype' again if the value is // already right, avoids reloading the syntax file. @@ -5191,12 +5284,14 @@ int makeset(FILE *fd, int opt_flags, int local_only) (p->flags & P_EXPAND) != 0) == FAIL) return FAIL; if (do_endif) { - if (put_line(fd, "endif") == FAIL) + if (put_line(fd, "endif") == FAIL) { return FAIL; + } } } } } + } } return OK; } @@ -5207,19 +5302,20 @@ int makeset(FILE *fd, int opt_flags, int local_only) */ int makefoldset(FILE *fd) { - if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, FALSE) == FAIL - || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, FALSE) + if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, false) == FAIL + || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, false) == FAIL - || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, FALSE) + || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, false) == FAIL - || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, FALSE) + || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, false) == FAIL || put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL || put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL || put_setnum(fd, "setlocal", "fdn", &curwin->w_p_fdn) == FAIL || put_setbool(fd, "setlocal", "fen", curwin->w_p_fen) == FAIL - ) + ) { return FAIL; + } return OK; } @@ -5229,8 +5325,9 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e char_u *s; char_u *buf; - if (fprintf(fd, "%s %s=", cmd, name) < 0) + if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; + } if (*valuep != NULL) { /* Output 'pastetoggle' as key names. For other * options some characters have to be escaped with @@ -5246,17 +5343,19 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e } } else if (expand) { buf = xmalloc(MAXPATHL); - home_replace(NULL, *valuep, buf, MAXPATHL, FALSE); + home_replace(NULL, *valuep, buf, MAXPATHL, false); if (put_escstr(fd, buf, 2) == FAIL) { xfree(buf); return FAIL; } xfree(buf); - } else if (put_escstr(fd, *valuep, 2) == FAIL) + } else if (put_escstr(fd, *valuep, 2) == FAIL) { return FAIL; + } } - if (put_eol(fd) < 0) + if (put_eol(fd) < 0) { return FAIL; + } return OK; } @@ -5264,26 +5363,32 @@ static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep) { long wc; - if (fprintf(fd, "%s %s=", cmd, name) < 0) + if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; + } if (wc_use_keyname((char_u *)valuep, &wc)) { - /* print 'wildchar' and 'wildcharm' as a key name */ - if (fputs((char *)get_special_key_name((int)wc, 0), fd) < 0) + // print 'wildchar' and 'wildcharm' as a key name + if (fputs((char *)get_special_key_name((int)wc, 0), fd) < 0) { return FAIL; - } else if (fprintf(fd, "%" PRId64, (int64_t)*valuep) < 0) + } + } else if (fprintf(fd, "%" PRId64, (int64_t)(*valuep)) < 0) { return FAIL; - if (put_eol(fd) < 0) + } + if (put_eol(fd) < 0) { return FAIL; + } return OK; } static int put_setbool(FILE *fd, char *cmd, char *name, int value) { - if (value < 0) /* global/local option using global value */ + if (value < 0) { // global/local option using global value return OK; + } if (fprintf(fd, "%s %s%s", cmd, value ? "" : "no", name) < 0 - || put_eol(fd) < 0) + || put_eol(fd) < 0) { return FAIL; + } return OK; } @@ -5294,7 +5399,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) * of 'ru_col'. */ -#define COL_RULER 17 /* columns needed by standard ruler */ +#define COL_RULER 17 // columns needed by standard ruler void comp_col(void) { @@ -5304,14 +5409,16 @@ void comp_col(void) ru_col = 0; if (p_ru) { ru_col = (ru_wid ? ru_wid : COL_RULER) + 1; - /* no last status line, adjust sc_col */ - if (!last_has_status) + // no last status line, adjust sc_col + if (!last_has_status) { sc_col = ru_col; + } } if (p_sc) { sc_col += SHOWCMD_COLS; - if (!p_ru || last_has_status) /* no need for separating space */ - ++sc_col; + if (!p_ru || last_has_status) { // no need for separating space + sc_col++; + } } assert(sc_col >= 0 && INT_MIN + sc_col <= Columns @@ -5321,10 +5428,12 @@ void comp_col(void) && INT_MIN + ru_col <= Columns && Columns - ru_col <= INT_MAX); ru_col = (int)(Columns - ru_col); - if (sc_col <= 0) /* screen too narrow, will become a mess */ + if (sc_col <= 0) { // screen too narrow, will become a mess sc_col = 1; - if (ru_col <= 0) + } + if (ru_col <= 0) { ru_col = 1; + } } // Unset local option value, similar to ":set opt<". @@ -5411,8 +5520,9 @@ void unset_global_local_option(char *name, void *from) static char_u *get_varp_scope(vimoption_T *p, int opt_flags) { if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { - if (p->var == VAR_WIN) + if (p->var == VAR_WIN) { return (char_u *)GLOBAL_WO(get_varp(p)); + } return p->var; } if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { @@ -5437,7 +5547,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); case PV_MENC: return (char_u *)&(curbuf->b_p_menc); } - return NULL; /* "cannot happen" */ + return NULL; // "cannot happen" } return get_varp(p); } @@ -5447,14 +5557,15 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) */ static char_u *get_varp(vimoption_T *p) { - /* hidden option, always return NULL */ - if (p->var == NULL) + // hidden option, always return NULL + if (p->var == NULL) { return NULL; + } switch ((int)p->indir) { case PV_NONE: return p->var; - /* global option with local value: use local value if it's been set */ + // global option with local value: use local value if it's been set case PV_EP: return *curbuf->b_p_ep != NUL ? (char_u *)&curbuf->b_p_ep : p->var; case PV_KP: return *curbuf->b_p_kp != NUL @@ -5593,7 +5704,7 @@ static char_u *get_varp(vimoption_T *p) case PV_LCS: return (char_u *)&(curwin->w_p_lcs); default: IEMSG(_("E356: get_varp ERROR")); } - /* always return a valid pointer to avoid a crash! */ + // always return a valid pointer to avoid a crash! return (char_u *)&(curbuf->b_p_wm); } @@ -5602,8 +5713,9 @@ static char_u *get_varp(vimoption_T *p) */ char_u *get_equalprg(void) { - if (*curbuf->b_p_ep == NUL) + if (*curbuf->b_p_ep == NUL) { return p_ep; + } return curbuf->b_p_ep; } @@ -5746,33 +5858,34 @@ void didset_window_options(win_T *wp) */ void buf_copy_options(buf_T *buf, int flags) { - int should_copy = TRUE; - char_u *save_p_isk = NULL; /* init for GCC */ + int should_copy = true; + char_u *save_p_isk = NULL; // init for GCC int dont_do_help; - int did_isk = FALSE; + int did_isk = false; /* * Skip this when the option defaults have not been set yet. Happens when * main() allocates the first buffer. */ if (p_cpo != NULL) { - /* - * Always copy when entering and 'cpo' contains 'S'. - * Don't copy when already initialized. - * Don't copy when 'cpo' contains 's' and not entering. - * 'S' BCO_ENTER initialized 's' should_copy - * yes yes X X TRUE - * yes no yes X FALSE - * no X yes X FALSE - * X no no yes FALSE - * X no no no TRUE - * no yes no X TRUE - */ + // + // Always copy when entering and 'cpo' contains 'S'. + // Don't copy when already initialized. + // Don't copy when 'cpo' contains 's' and not entering. + // 'S' BCO_ENTER initialized 's' should_copy + // yes yes X X true + // yes no yes X false + // no X yes X false + // X no no yes false + // X no no no true + // no yes no X true + /// if ((vim_strchr(p_cpo, CPO_BUFOPTGLOB) == NULL || !(flags & BCO_ENTER)) && (buf->b_p_initialized || (!(flags & BCO_ENTER) - && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - should_copy = FALSE; + && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) { + should_copy = false; + } if (should_copy || (flags & BCO_ALWAYS)) { /* Don't copy the options specific to a help buffer when @@ -5780,7 +5893,7 @@ void buf_copy_options(buf_T *buf, int flags) * (jumping back to a help file with CTRL-T or CTRL-O) */ dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized; - if (dont_do_help) { /* don't free b_p_isk */ + if (dont_do_help) { // don't free b_p_isk save_p_isk = buf->b_p_isk; buf->b_p_isk = NULL; } @@ -5789,8 +5902,8 @@ void buf_copy_options(buf_T *buf, int flags) * If not already initialized, set 'readonly' and copy 'fileformat'. */ if (!buf->b_p_initialized) { - free_buf_options(buf, TRUE); - buf->b_p_ro = FALSE; /* don't copy readonly */ + free_buf_options(buf, true); + buf->b_p_ro = false; // don't copy readonly buf->b_p_fenc = vim_strsave(p_fenc); switch (*p_ffs) { case 'm': { @@ -5853,12 +5966,12 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_cin = p_cin; buf->b_p_cink = vim_strsave(p_cink); buf->b_p_cino = vim_strsave(p_cino); - /* Don't copy 'filetype', it must be detected */ + // Don't copy 'filetype', it must be detected buf->b_p_ft = empty_option; buf->b_p_pi = p_pi; buf->b_p_cinw = vim_strsave(p_cinw); buf->b_p_lisp = p_lisp; - /* Don't copy 'syntax', it must be set */ + // Don't copy 'syntax', it must be set buf->b_p_syn = empty_option; buf->b_p_smc = p_smc; buf->b_s.b_syn_isk = empty_option; @@ -5909,15 +6022,16 @@ void buf_copy_options(buf_T *buf, int flags) * Don't touch these at all when BCO_NOHELP is used and going from * or to a help buffer. */ - if (dont_do_help) + if (dont_do_help) { buf->b_p_isk = save_p_isk; - else { + } else { buf->b_p_isk = vim_strsave(p_isk); did_isk = true; buf->b_p_ts = p_ts; buf->b_help = false; - if (buf->b_p_bt[0] == 'h') + if (buf->b_p_bt[0] == 'h') { clear_string_option(&buf->b_p_bt); + } buf->b_p_ma = p_ma; } } @@ -5926,13 +6040,15 @@ void buf_copy_options(buf_T *buf, int flags) * When the options should be copied (ignoring BCO_ALWAYS), set the * flag that indicates that the options have been initialized. */ - if (should_copy) + if (should_copy) { buf->b_p_initialized = true; + } } - check_buf_options(buf); /* make sure we don't have NULLs */ - if (did_isk) - (void)buf_init_chartab(buf, FALSE); + check_buf_options(buf); // make sure we don't have NULLs + if (did_isk) { + (void)buf_init_chartab(buf, false); + } } /* @@ -5970,19 +6086,19 @@ static int expand_option_idx = -1; static char_u expand_option_name[5] = {'t', '_', NUL, NUL, NUL}; static int expand_option_flags = 0; -void -set_context_in_set_cmd ( +void +set_context_in_set_cmd( expand_T *xp, char_u *arg, - int opt_flags /* OPT_GLOBAL and/or OPT_LOCAL */ + int opt_flags // OPT_GLOBAL and/or OPT_LOCAL ) { char_u nextchar; - uint32_t flags = 0; /* init for GCC */ - int opt_idx = 0; /* init for GCC */ + uint32_t flags = 0; // init for GCC + int opt_idx = 0; // init for GCC char_u *p; char_u *s; - int is_term_option = FALSE; + int is_term_option = false; int key; expand_option_flags = opt_flags; @@ -5999,17 +6115,18 @@ set_context_in_set_cmd ( } while (p > arg) { s = p; - /* count number of backslashes before ' ' or ',' */ + // count number of backslashes before ' ' or ',' if (*p == ' ' || *p == ',') { - while (s > arg && *(s - 1) == '\\') - --s; + while (s > arg && *(s - 1) == '\\') { + s--; + } } - /* break at a space with an even number of backslashes */ + // break at a space with an even number of backslashes if (*p == ' ' && ((p - s) & 1) == 0) { - ++p; + p++; break; } - --p; + p--; } if (STRNCMP(p, "no", 2) == 0) { xp->xp_context = EXPAND_BOOL_SETTINGS; @@ -6021,27 +6138,31 @@ set_context_in_set_cmd ( } xp->xp_pattern = arg = p; if (*arg == '<') { - while (*p != '>') - if (*p++ == NUL) /* expand terminal option name */ + while (*p != '>') { + if (*p++ == NUL) { // expand terminal option name return; + } + } key = get_special_key_code(arg + 1); - if (key == 0) { /* unknown name */ + if (key == 0) { // unknown name xp->xp_context = EXPAND_NOTHING; return; } nextchar = *++p; - is_term_option = TRUE; + is_term_option = true; expand_option_name[2] = (char_u)KEY2TERMCAP0(key); expand_option_name[3] = KEY2TERMCAP1(key); } else { if (p[0] == 't' && p[1] == '_') { p += 2; - if (*p != NUL) - ++p; - if (*p == NUL) - return; /* expand option name */ + if (*p != NUL) { + p++; + } + if (*p == NUL) { + return; // expand option name + } nextchar = *++p; - is_term_option = TRUE; + is_term_option = true; expand_option_name[2] = p[-2]; expand_option_name[3] = p[-1]; } else { @@ -6065,9 +6186,9 @@ set_context_in_set_cmd ( } } } - /* handle "-=" and "+=" */ + // handle "-=" and "+=" if ((nextchar == '-' || nextchar == '+' || nextchar == '^') && p[1] == '=') { - ++p; + p++; nextchar = '='; } if ((nextchar != '=' && nextchar != ':') @@ -6077,16 +6198,18 @@ set_context_in_set_cmd ( } if (p[1] == NUL) { xp->xp_context = EXPAND_OLD_SETTING; - if (is_term_option) + if (is_term_option) { expand_option_idx = -1; - else + } else { expand_option_idx = opt_idx; + } xp->xp_pattern = p + 1; return; } xp->xp_context = EXPAND_NOTHING; - if (is_term_option || (flags & P_NUM)) + if (is_term_option || (flags & P_NUM)) { return; + } xp->xp_pattern = p + 1; @@ -6109,22 +6232,24 @@ set_context_in_set_cmd ( xp->xp_backslash = XP_BS_ONE; } else { xp->xp_context = EXPAND_FILES; - /* for 'tags' need three backslashes for a space */ - if (p == (char_u *)&p_tags) + // for 'tags' need three backslashes for a space + if (p == (char_u *)&p_tags) { xp->xp_backslash = XP_BS_THREE; - else + } else { xp->xp_backslash = XP_BS_ONE; + } } } /* For an option that is a list of file names, find the start of the * last file name. */ - for (p = arg + STRLEN(arg) - 1; p > xp->xp_pattern; --p) { - /* count number of backslashes before ' ' or ',' */ + for (p = arg + STRLEN(arg) - 1; p > xp->xp_pattern; p--) { + // count number of backslashes before ' ' or ',' if (*p == ' ' || *p == ',') { s = p; - while (s > xp->xp_pattern && *(s - 1) == '\\') - --s; + while (s > xp->xp_pattern && *(s - 1) == '\\') { + s--; + } if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) { xp->xp_pattern = p + 1; @@ -6132,7 +6257,7 @@ set_context_in_set_cmd ( } } - /* for 'spellsuggest' start at "file:" */ + // for 'spellsuggest' start at "file:" if (options[opt_idx].var == (char_u *)&p_sps && STRNCMP(p, "file:", 5) == 0) { xp->xp_pattern = p + 5; @@ -6157,31 +6282,36 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u *** * loop == 0: count the number of matching options * loop == 1: copy the matching options into allocated memory */ - for (loop = 0; loop <= 1; ++loop) { + for (loop = 0; loop <= 1; loop++) { regmatch->rm_ic = ic; if (xp->xp_context != EXPAND_BOOL_SETTINGS) { for (match = 0; match < (int)ARRAY_SIZE(names); - ++match) + match++) { if (vim_regexec(regmatch, (char_u *)names[match], (colnr_T)0)) { - if (loop == 0) + if (loop == 0) { num_normal++; - else + } else { (*file)[count++] = vim_strsave((char_u *)names[match]); + } } + } } for (size_t opt_idx = 0; (str = (char_u *)options[opt_idx].fullname) != NULL; opt_idx++) { - if (options[opt_idx].var == NULL) + if (options[opt_idx].var == NULL) { continue; + } if (xp->xp_context == EXPAND_BOOL_SETTINGS - && !(options[opt_idx].flags & P_BOOL)) + && !(options[opt_idx].flags & P_BOOL)) { continue; - match = FALSE; + } + match = false; if (vim_regexec(regmatch, str, (colnr_T)0) || (options[opt_idx].shortname != NULL && vim_regexec(regmatch, - (char_u *)options[opt_idx].shortname, (colnr_T)0))){ - match = TRUE; + (char_u *)options[opt_idx].shortname, + (colnr_T)0))) { + match = true; } if (match) { @@ -6252,10 +6382,10 @@ void ExpandOldSetting(int *num_file, char_u ***file) * Get the value for the numeric or string option *opp in a nice format into * NameBuff[]. Must not be called with a hidden option! */ -static void -option_value2string ( +static void +option_value2string( vimoption_T *opp, - int opt_flags /* OPT_GLOBAL and/or OPT_LOCAL */ + int opt_flags // OPT_GLOBAL and/or OPT_LOCAL ) { char_u *varp; @@ -6290,19 +6420,18 @@ option_value2string ( } } -/* - * Return TRUE if "varp" points to 'wildchar' or 'wildcharm' and it can be - * printed as a keyname. - * "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'. - */ +/// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be +/// printed as a keyname. +/// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'. static int wc_use_keyname(char_u *varp, long *wcp) { if (((long *)varp == &p_wc) || ((long *)varp == &p_wcm)) { *wcp = *(long *)varp; - if (IS_SPECIAL(*wcp) || find_special_key_in_table((int)*wcp) >= 0) - return TRUE; + if (IS_SPECIAL(*wcp) || find_special_key_in_table((int)(*wcp)) >= 0) { + return true; + } } - return FALSE; + return false; } /* @@ -6314,7 +6443,7 @@ static int wc_use_keyname(char_u *varp, long *wcp) * * langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim * commands. - * langmap_mapga.ga_data is a sorted table of langmap_entry_T. + * langmap_mapga.ga_data is a sorted table of langmap_entry_T. * This does the same as langmap_mapchar[] for characters >= 256. */ /* @@ -6338,7 +6467,7 @@ static void langmap_set_entry(int from, int to) assert(langmap_mapga.ga_len >= 0); unsigned int b = (unsigned int)langmap_mapga.ga_len; - /* Do a binary search for an existing entry. */ + // Do a binary search for an existing entry. while (a != b) { unsigned int i = (a + b) / 2; int d = entries[i].from - from; @@ -6347,19 +6476,20 @@ static void langmap_set_entry(int from, int to) entries[i].to = to; return; } - if (d < 0) + if (d < 0) { a = i + 1; - else + } else { b = i; + } } ga_grow(&langmap_mapga, 1); - /* insert new entry at position "a" */ + // insert new entry at position "a" entries = (langmap_entry_T *)(langmap_mapga.ga_data) + a; memmove(entries + 1, entries, ((unsigned int)langmap_mapga.ga_len - a) * sizeof(langmap_entry_T)); - ++langmap_mapga.ga_len; + langmap_mapga.ga_len++; entries[0].from = from; entries[0].to = to; } @@ -6377,20 +6507,23 @@ int langmap_adjust_mb(int c) int i = (a + b) / 2; int d = entries[i].from - c; - if (d == 0) - return entries[i].to; /* found matching entry */ - if (d < 0) + if (d == 0) { + return entries[i].to; // found matching entry + } + if (d < 0) { a = i + 1; - else + } else { b = i; + } } - return c; /* no entry found, return "c" unmodified */ + return c; // no entry found, return "c" unmodified } static void langmap_init(void) { - for (int i = 0; i < 256; i++) - langmap_mapchar[i] = (char_u)i; /* we init with a one-to-one map */ + for (int i = 0; i < 256; i++) { + langmap_mapchar[i] = (char_u)i; // we init with a one-to-one map + } ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8); } @@ -6404,8 +6537,8 @@ static void langmap_set(void) char_u *p2; int from, to; - ga_clear(&langmap_mapga); /* clear the previous map first */ - langmap_init(); /* back to one-to-one map */ + ga_clear(&langmap_mapga); // clear the previous map first + langmap_init(); // back to one-to-one map for (p = p_langmap; p[0] != NUL; ) { for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';'; @@ -6414,13 +6547,14 @@ static void langmap_set(void) p2++; } } - if (p2[0] == ';') - ++p2; /* abcd;ABCD form, p2 points to A */ - else - p2 = NULL; /* aAbBcCdD form, p2 is NULL */ + if (p2[0] == ';') { + p2++; // abcd;ABCD form, p2 points to A + } else { + p2 = NULL; // aAbBcCdD form, p2 is NULL + } while (p[0]) { if (p[0] == ',') { - ++p; + p++; break; } if (p[0] == '\\' && p[1] != NUL) { @@ -6450,9 +6584,9 @@ static void langmap_set(void) return; } - if (from >= 256) + if (from >= 256) { langmap_set_entry(from, to); - else { + } else { assert(to <= UCHAR_MAX); langmap_mapchar[from & 255] = (char_u)to; } @@ -6470,7 +6604,7 @@ static void langmap_set(void) p); return; } - ++p; + p++; } break; } @@ -6479,14 +6613,13 @@ static void langmap_set(void) } } -/* - * Return TRUE if format option 'x' is in effect. - * Take care of no formatting when 'paste' is set. - */ +/// Return true if format option 'x' is in effect. +/// Take care of no formatting when 'paste' is set. int has_format_option(int x) { - if (p_paste) - return FALSE; + if (p_paste) { + return false; + } return vim_strchr(curbuf->b_p_fo, x) != NULL; } @@ -6505,7 +6638,7 @@ bool shortmess(int x) */ static void paste_option_changed(void) { - static int old_p_paste = FALSE; + static int old_p_paste = false; static int save_sm = 0; static int save_sta = 0; static int save_ru = 0; @@ -6518,7 +6651,7 @@ static void paste_option_changed(void) * Save the current values, so they can be restored later. */ if (!old_p_paste) { - /* save options for each buffer */ + // save options for each buffer FOR_ALL_BUFFERS(buf) { buf->b_p_tw_nopaste = buf->b_p_tw; buf->b_p_wm_nopaste = buf->b_p_wm; @@ -6566,12 +6699,10 @@ static void paste_option_changed(void) p_wm = 0; p_sts = 0; p_ai = 0; - } - /* - * Paste switched from on to off: Restore saved values. - */ - else if (old_p_paste) { - /* restore options for each buffer */ + } else if (old_p_paste) { + // Paste switched from on to off: Restore saved values. + + // restore options for each buffer FOR_ALL_BUFFERS(buf) { buf->b_p_tw = buf->b_p_tw_nopaste; buf->b_p_wm = buf->b_p_wm_nopaste; @@ -6580,7 +6711,7 @@ static void paste_option_changed(void) buf->b_p_et = buf->b_p_et_nopaste; } - /* restore global options */ + // restore global options p_sm = save_sm; p_sta = save_sta; if (p_ru != save_ru) { @@ -6659,12 +6790,15 @@ static void fill_breakat_flags(void) char_u *p; int i; - for (i = 0; i < 256; i++) - breakat_flags[i] = FALSE; + for (i = 0; i < 256; i++) { + breakat_flags[i] = false; + } - if (p_breakat != NULL) - for (p = p_breakat; *p; p++) - breakat_flags[*p] = TRUE; + if (p_breakat != NULL) { + for (p = p_breakat; *p; p++) { + breakat_flags[*p] = true; + } + } } /* @@ -6676,7 +6810,7 @@ static void fill_breakat_flags(void) static int check_opt_strings( char_u *val, char **values, - int list /* when TRUE: accept a list of values */ + int list // when true: accept a list of values ) { return opt_strings_flags(val, values, NULL, list); @@ -6690,18 +6824,19 @@ static int check_opt_strings( * Empty is always OK. */ static int opt_strings_flags( - char_u *val, /* new value */ - char **values, /* array of valid string values */ + char_u *val, // new value + char **values, // array of valid string values unsigned *flagp, - bool list /* when TRUE: accept a list of values */ + bool list // when true: accept a list of values ) { unsigned int new_flags = 0; while (*val) { - for (unsigned int i = 0;; ++i) { - if (values[i] == NULL) /* val not found in values[] */ + for (unsigned int i = 0;; i++) { + if (values[i] == NULL) { // val not found in values[] return FAIL; + } size_t len = STRLEN(values[i]); if (STRNCMP(values[i], val, len) == 0 @@ -6709,12 +6844,13 @@ static int opt_strings_flags( val += len + (val[len] == ','); assert(i < sizeof(1U) * 8); new_flags |= (1U << i); - break; /* check next item in val list */ + break; // check next item in val list } } } - if (flagp != NULL) + if (flagp != NULL) { *flagp = new_flags; + } return OK; } @@ -6729,55 +6865,60 @@ static int check_opt_wim(void) int i; int idx = 0; - for (i = 0; i < 4; ++i) + for (i = 0; i < 4; i++) { new_wim_flags[i] = 0; + } - for (p = p_wim; *p; ++p) { - for (i = 0; ASCII_ISALPHA(p[i]); ++i) - ; - if (p[i] != NUL && p[i] != ',' && p[i] != ':') + for (p = p_wim; *p; p++) { + for (i = 0; ASCII_ISALPHA(p[i]); i++) {} + if (p[i] != NUL && p[i] != ',' && p[i] != ':') { return FAIL; - if (i == 7 && STRNCMP(p, "longest", 7) == 0) + } + if (i == 7 && STRNCMP(p, "longest", 7) == 0) { new_wim_flags[idx] |= WIM_LONGEST; - else if (i == 4 && STRNCMP(p, "full", 4) == 0) + } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { new_wim_flags[idx] |= WIM_FULL; - else if (i == 4 && STRNCMP(p, "list", 4) == 0) + } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { new_wim_flags[idx] |= WIM_LIST; - else + } else { return FAIL; + } p += i; - if (*p == NUL) + if (*p == NUL) { break; + } if (*p == ',') { - if (idx == 3) + if (idx == 3) { return FAIL; - ++idx; + } + idx++; } } - /* fill remaining entries with last flag */ + // fill remaining entries with last flag while (idx < 3) { new_wim_flags[idx + 1] = new_wim_flags[idx]; - ++idx; + idx++; } - /* only when there are no errors, wim_flags[] is changed */ - for (i = 0; i < 4; ++i) + // only when there are no errors, wim_flags[] is changed + for (i = 0; i < 4; i++) { wim_flags[i] = new_wim_flags[i]; + } return OK; } /* * Check if backspacing over something is allowed. - * The parameter what is one of the following: whatBS_INDENT, BS_EOL + * The parameter what is one of the following: whatBS_INDENT, BS_EOL * or BS_START */ bool can_bs(int what) { switch (*p_bs) { - case '2': return TRUE; + case '2': return true; case '1': return what != BS_START; - case '0': return FALSE; + case '0': return false; } return vim_strchr(p_bs, what) != NULL; } @@ -6792,7 +6933,7 @@ void save_file_ff(buf_T *buf) buf->b_start_eol = buf->b_p_eol; buf->b_start_bomb = buf->b_p_bomb; - /* Only use free/alloc when necessary, they take time. */ + // Only use free/alloc when necessary, they take time. if (buf->b_start_fenc == NULL || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { xfree(buf->b_start_fenc); @@ -6800,33 +6941,37 @@ void save_file_ff(buf_T *buf) } } -/* - * Return TRUE if 'fileformat' and/or 'fileencoding' has a different value - * from when editing started (save_file_ff() called). - * Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was - * changed and 'binary' is not set. - * Also when 'endofline' was changed and 'fixeol' is not set. - * When "ignore_empty" is true don't consider a new, empty buffer to be - * changed. - */ +/// Return true if 'fileformat' and/or 'fileencoding' has a different value +/// from when editing started (save_file_ff() called). +/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was +/// changed and 'binary' is not set. +/// Also when 'endofline' was changed and 'fixeol' is not set. +/// When "ignore_empty" is true don't consider a new, empty buffer to be +/// changed. bool file_ff_differs(buf_T *buf, bool ignore_empty) { - /* In a buffer that was never loaded the options are not valid. */ - if (buf->b_flags & BF_NEVERLOADED) - return FALSE; + // In a buffer that was never loaded the options are not valid. + if (buf->b_flags & BF_NEVERLOADED) { + return false; + } if (ignore_empty && (buf->b_flags & BF_NEW) && buf->b_ml.ml_line_count == 1 - && *ml_get_buf(buf, (linenr_T)1, FALSE) == NUL) - return FALSE; - if (buf->b_start_ffc != *buf->b_p_ff) + && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { + return false; + } + if (buf->b_start_ffc != *buf->b_p_ff) { + return true; + } + if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) { return true; - if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) + } + if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { return true; - if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) - return TRUE; - if (buf->b_start_fenc == NULL) + } + if (buf->b_start_fenc == NULL) { return *buf->b_p_fenc != NUL; + } return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; } @@ -6835,7 +6980,7 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty) */ int check_ff_value(char_u *p) { - return check_opt_strings(p, p_ff_values, FALSE); + return check_opt_strings(p, p_ff_values, false); } /* @@ -6862,7 +7007,7 @@ int get_sts_value(void) * Check matchpairs option for "*initc". * If there is a match set "*initc" to the matching character and "*findc" to * the opposite character. Set "*backwards" to the direction. - * When "switchit" is TRUE swap the direction. + * When "switchit" is true swap the direction. */ void find_mps_values(int *initc, int *findc, int *backwards, int switchit) { @@ -6927,10 +7072,12 @@ static bool briopt_check(win_T *wp) p += 3; bri_sbr = true; } - if (*p != ',' && *p != NUL) + if (*p != ',' && *p != NUL) { return false; - if (*p == ',') - ++p; + } + if (*p == ',') { + p++; + } } wp->w_p_brishift = bri_shift; @@ -7086,22 +7233,40 @@ size_t copy_option_part(char_u **option, char_u *buf, size_t maxlen, return len; } -/// Return TRUE when 'shell' has "csh" in the tail. +/// Return true when 'shell' has "csh" in the tail. int csh_like_shell(void) { return strstr((char *)path_tail(p_sh), "csh") != NULL; } -/// Return true when window "wp" has a column to draw signs in. -bool signcolumn_on(win_T *wp) +/// Return the number of requested sign columns, based on current +/// buffer signs and on user configuration. +int win_signcol_count(win_T *wp) { - if (*wp->w_p_scl == 'n') { - return false; - } - if (*wp->w_p_scl == 'y') { - return true; - } - return wp->w_buffer->b_signlist != NULL; + int maximum = 1, needed_signcols; + const char *scl = (const char *)wp->w_p_scl; + + if (*scl == 'n') { + return 0; + } + needed_signcols = buf_signcols(wp->w_buffer); + + // yes or yes + if (!strncmp(scl, "yes:", 4)) { + // Fixed amount of columns + return scl[4] - '0'; + } + if (*scl == 'y') { + return 1; + } + + // auto or auto:<NUM> + if (!strncmp(scl, "auto:", 5)) { + // Variable depending on a configuration + maximum = scl[5] - '0'; + } + + return MIN(maximum, needed_signcols); } /// Get window or buffer local options diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 6dc5e418fc..3e116095fd 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -74,13 +74,14 @@ #define FO_MBYTE_JOIN 'M' /* no space before/after multi-byte char */ #define FO_MBYTE_JOIN2 'B' /* no space between multi-byte chars */ #define FO_ONE_LETTER '1' -#define FO_WHITE_PAR 'w' /* trailing white space continues paragr. */ -#define FO_AUTO 'a' /* automatic formatting */ -#define FO_REMOVE_COMS 'j' /* remove comment leaders when joining lines */ +#define FO_WHITE_PAR 'w' // trailing white space continues paragr. +#define FO_AUTO 'a' // automatic formatting +#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines +#define FO_PERIOD_ABBR 'p' // don't break a single space after a period #define DFLT_FO_VI "vt" #define DFLT_FO_VIM "tcqj" -#define FO_ALL "tcroq2vlb1mMBn,awj" /* for do_set() */ +#define FO_ALL "tcroq2vlb1mMBn,awjp" // for do_set() // characters for the p_cpo option: #define CPO_ALTREAD 'a' // ":read" sets alternate file name @@ -178,14 +179,6 @@ enum { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ 0, \ }) -/// All possible flags for 'shm'. -#define SHM_ALL ((char_u[]) { \ - SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ - SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, SHM_OVER, \ - SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, SHM_COMPLETIONMENU, \ - SHM_RECORDING, SHM_FILEINFO, \ - 0, \ -}) /* characters for p_go: */ #define GO_ASEL 'a' /* autoselect */ @@ -637,6 +630,7 @@ EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' EXTERN char_u *p_shada; ///< 'shada' +EXTERN char *p_shadafile; ///< 'shadafile' EXTERN char_u *p_vdir; ///< 'viewdir' EXTERN char_u *p_vop; ///< 'viewoptions' EXTERN unsigned vop_flags; ///< uses SSOP_ flags @@ -658,6 +652,12 @@ extern char_u *p_vfile; /* 'verbosefile' */ #endif EXTERN int p_warn; // 'warn' EXTERN char_u *p_wop; // 'wildoptions' +EXTERN unsigned wop_flags; +# ifdef IN_OPTION_C +static char *(p_wop_values[]) = { "tagfile", "pum", NULL }; +#endif +#define WOP_TAGFILE 0x01 +#define WOP_PUM 0x02 EXTERN long p_window; // 'window' EXTERN char_u *p_wak; // 'winaltkeys' EXTERN char_u *p_wig; // 'wildignore' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 2398f9d61c..affddd8084 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -44,6 +44,8 @@ local N_=function(s) return 'N_(' .. cstr(s) .. ')' end end +-- used for 'cinkeys' and 'indentkeys' +local indentkeys_default = '0{,0},0),0],:,0#,!^F,o,O,e'; return { cstr=cstr, options={ @@ -179,6 +181,7 @@ return { { full_name='backupskip', abbreviation='bsk', type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, vi_def=true, varname='p_bsk', defaults={if_true={vi=""}} @@ -187,6 +190,7 @@ return { full_name='belloff', abbreviation='bo', deny_duplicates=true, type='string', list='comma', scope={'global'}, + deny_duplicates=true, vi_def=true, varname='p_bo', defaults={if_true={vi="all"}} @@ -319,7 +323,7 @@ return { vi_def=true, alloced=true, varname='p_cink', - defaults={if_true={vi="0{,0},0),:,0#,!^F,o,O,e"}} + defaults={if_true={vi=indentkeys_default}} }, { full_name='cinoptions', abbreviation='cino', @@ -754,6 +758,7 @@ return { { full_name='fileencodings', abbreviation='fencs', type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, vi_def=true, varname='p_fencs', defaults={if_true={vi="ucs-bom,utf-8,default,latin1"}} @@ -1014,6 +1019,7 @@ return { { full_name='guifontset', abbreviation='gfs', type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, vi_def=true, varname='p_guifontset', redraw={'ui_option'}, @@ -1068,6 +1074,7 @@ return { { full_name='helplang', abbreviation='hlg', type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, vi_def=true, varname='p_hlg', defaults={if_true={vi=""}} @@ -1218,7 +1225,7 @@ return { vi_def=true, alloced=true, varname='p_indk', - defaults={if_true={vi="0{,0},:,0#,!^F,o,O,e"}} + defaults={if_true={vi=indentkeys_default}} }, { full_name='infercase', abbreviation='inf', @@ -2008,6 +2015,15 @@ return { defaults={if_true={vi="", vim="!,'100,<50,s10,h"}} }, { + full_name='shadafile', abbreviation='sdf', + type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, + vi_def=true, + secure=true, + varname='p_shadafile', + defaults={if_true={vi=""}} + }, + { full_name='shell', abbreviation='sh', type='string', scope={'global'}, secure=true, @@ -2238,6 +2254,7 @@ return { { full_name='spellfile', abbreviation='spf', type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, secure=true, vi_def=true, alloced=true, @@ -2248,6 +2265,7 @@ return { { full_name='spelllang', abbreviation='spl', type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, vi_def=true, alloced=true, expand=true, @@ -2258,6 +2276,7 @@ return { { full_name='spellsuggest', abbreviation='sps', type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, secure=true, vi_def=true, expand=true, @@ -2623,6 +2642,15 @@ return { defaults={if_true={vi="", vim="!,'100,<50,s10,h"}} }, { + full_name='viminfofile', abbreviation='vif', + type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, + vi_def=true, + secure=true, + varname='p_shadafile', + defaults={if_true={vi=""}} + }, + { full_name='virtualedit', abbreviation='ve', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, @@ -2699,7 +2727,8 @@ return { }, { full_name='wildoptions', abbreviation='wop', - type='string', scope={'global'}, + type='string', list='onecomma', scope={'global'}, + deny_duplicates=true, vi_def=true, varname='p_wop', defaults={if_true={vi=""}} diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index e7bfbc8240..7d1021962c 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -151,25 +151,36 @@ int os_unsetenv(const char *name) char *os_getenvname_at_index(size_t index) { #ifdef _WIN32 - // Check if index is inside the environ array and is not the last element. - for (size_t i = 0; i <= index; i++) { - if (_wenviron[i] == NULL) { - return NULL; - } - } - wchar_t *utf16_str = _wenviron[index]; - char *utf8_str; - int conversion_result = utf16_to_utf8(utf16_str, &utf8_str); - if (conversion_result != 0) { - EMSG2("utf16_to_utf8 failed: %d", conversion_result); + wchar_t *env = GetEnvironmentStringsW(); + if (!env) { return NULL; } - size_t namesize = 0; - while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) { - namesize++; + char *name = NULL; + size_t current_index = 0; + // GetEnvironmentStringsW() result has this format: + // var1=value1\0var2=value2\0...varN=valueN\0\0 + for (wchar_t *it = env; *it != L'\0' || *(it + 1) != L'\0'; it++) { + if (index == current_index) { + char *utf8_str; + int conversion_result = utf16_to_utf8(it, &utf8_str); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %d", conversion_result); + break; + } + size_t namesize = 0; + while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) { + namesize++; + } + name = (char *)vim_strnsave((char_u *)utf8_str, namesize); + xfree(utf8_str); + break; + } + if (*it == L'\0') { + current_index++; + } } - char *name = (char *)vim_strnsave((char_u *)utf8_str, namesize); - xfree(utf8_str); + + FreeEnvironmentStringsW(env); return name; #else # if defined(HAVE__NSGETENVIRON) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 9b9b4581f4..8c7dfcdee7 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -226,13 +226,13 @@ int os_exepath(char *buffer, size_t *size) return uv_exepath(buffer, size); } -/// Checks if the given path represents an executable file. +/// Checks if the file `name` is executable. /// -/// @param[in] name Name of the executable. -/// @param[out] abspath Path of the executable, if found and not `NULL`. -/// @param[in] use_path If 'false', only check if "name" is executable +/// @param[in] name Filename to check. +/// @param[out] abspath Returns resolved executable path, if not NULL. +/// @param[in] use_path Also search $PATH. /// -/// @return `true` if `name` is executable and +/// @return true if `name` is executable and /// - can be found in $PATH, /// - is relative to current dir or /// - is absolute. @@ -242,40 +242,36 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { bool no_path = !use_path || path_is_absolute(name); -#ifndef WIN32 // If the filename is "qualified" (relative or absolute) do not check $PATH. +#ifdef WIN32 + no_path |= (name[0] == '.' + && ((name[1] == '/' || name[1] == '\\') + || (name[1] == '.' && (name[2] == '/' || name[2] == '\\')))); +#else no_path |= (name[0] == '.' && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))); #endif if (no_path) { #ifdef WIN32 - const char *pathext = os_getenv("PATHEXT"); - if (!pathext) { - pathext = ".com;.exe;.bat;.cmd"; - } - bool ok = is_executable((char *)name) || is_executable_ext((char *)name, - pathext); + if (is_executable_ext((char *)name, abspath)) { #else // Must have path separator, cannot execute files in the current directory. - const bool ok = ((const char_u *)gettail_dir((const char *)name) != name - && is_executable((char *)name)); + if ((const char_u *)gettail_dir((const char *)name) != name + && is_executable((char *)name, abspath)) { #endif - if (ok) { - if (abspath != NULL) { - *abspath = save_abs_path(name); - } return true; + } else { + return false; } - return false; } return is_executable_in_path(name, abspath); } /// Returns true if `name` is an executable file. -static bool is_executable(const char *name) - FUNC_ATTR_NONNULL_ALL +static bool is_executable(const char *name, char_u **abspath) + FUNC_ATTR_NONNULL_ARG(1) { int32_t mode = os_getperm((const char *)name); @@ -286,40 +282,58 @@ static bool is_executable(const char *name) #ifdef WIN32 // Windows does not have exec bit; just check if the file exists and is not // a directory. - return (S_ISREG(mode)); + const bool ok = S_ISREG(mode); #else int r = -1; if (S_ISREG(mode)) { RUN_UV_FS_FUNC(r, uv_fs_access, name, X_OK, NULL); } - return (r == 0); + const bool ok = (r == 0); #endif + if (ok && abspath != NULL) { + *abspath = save_abs_path((char_u *)name); + } + return ok; } #ifdef WIN32 -/// Appends file extensions from `pathext` to `name` and returns true if any -/// such combination is executable. -static bool is_executable_ext(char *name, const char *pathext) - FUNC_ATTR_NONNULL_ALL +/// Checks if file `name` is executable under any of these conditions: +/// - extension is in $PATHEXT and `name` is executable +/// - result of any $PATHEXT extension appended to `name` is executable +static bool is_executable_ext(char *name, char_u **abspath) + FUNC_ATTR_NONNULL_ARG(1) { + const bool is_unix_shell = strstr((char *)path_tail(p_sh), "sh") != NULL; + char *nameext = strrchr(name, '.'); + size_t nameext_len = nameext ? strlen(nameext) : 0; xstrlcpy(os_buf, name, sizeof(os_buf)); char *buf_end = xstrchrnul(os_buf, '\0'); + const char *pathext = os_getenv("PATHEXT"); + if (!pathext) { + pathext = ".com;.exe;.bat;.cmd"; + } for (const char *ext = pathext; *ext; ext++) { - // Skip the extension if there is no suffix after a '.'. + // If $PATHEXT itself contains dot: if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) { + if (is_executable(name, abspath)) { + return true; + } + // Skip it. ext++; continue; } const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR); - STRLCPY(buf_end, ext, ext_end - ext + 1); - - if (is_executable(os_buf)) { - return true; - } - - if (*ext_end != ENV_SEPCHAR) { - break; + size_t ext_len = (size_t)(ext_end - ext); + if (ext_len != 0) { + STRLCPY(buf_end, ext, ext_len + 1); + bool in_pathext = nameext_len == ext_len + && 0 == mb_strnicmp((char_u *)nameext, (char_u *)ext, ext_len); + + if (((in_pathext || is_unix_shell) && is_executable(name, abspath)) + || is_executable(os_buf, abspath)) { + return true; + } } ext = ext_end; } @@ -327,10 +341,10 @@ static bool is_executable_ext(char *name, const char *pathext) } #endif -/// Checks if a file is inside the `$PATH` and is executable. +/// Checks if a file is in `$PATH` and is executable. /// -/// @param[in] name The name of the executable. -/// @param[out] abspath Path of the executable, if found and not `NULL`. +/// @param[in] name Filename to check. +/// @param[out] abspath Returns resolved executable path, if not NULL. /// /// @return `true` if `name` is an executable inside `$PATH`. static bool is_executable_in_path(const char_u *name, char_u **abspath) @@ -351,15 +365,6 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) #endif size_t buf_len = STRLEN(name) + strlen(path) + 2; - -#ifdef WIN32 - const char *pathext = os_getenv("PATHEXT"); - if (!pathext) { - pathext = ".com;.exe;.bat;.cmd"; - } - buf_len += strlen(pathext); -#endif - char *buf = xmalloc(buf_len); // Walk through all entries in $PATH to check if "name" exists there and @@ -374,15 +379,10 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) append_path(buf, (char *)name, buf_len); #ifdef WIN32 - bool ok = is_executable(buf) || is_executable_ext(buf, pathext); + if (is_executable_ext(buf, abspath)) { #else - bool ok = is_executable(buf); + if (is_executable(buf, abspath)) { #endif - if (ok) { - if (abspath != NULL) { // Caller asked for a copy of the path. - *abspath = save_abs_path((char_u *)buf); - } - rv = true; goto end; } @@ -643,8 +643,9 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, return (ptrdiff_t)written_bytes; } -/// Copies a file from path to new_path. Currently this passes -/// the arguments through to uv_fs_copyfile. +/// Copies a file from `path` to `new_path`. +/// +/// @see http://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_copyfile /// /// @param path Path of file to be copied /// @param path_new Path of new file 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 499ee11cad..58a0008e04 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -12,6 +12,7 @@ #include "nvim/vim.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/eval/typval.h" #include "nvim/popupmnu.h" #include "nvim/charset.h" #include "nvim/ex_cmds.h" @@ -65,7 +66,9 @@ static bool pum_invalid = false; // the screen was just cleared /// @param array_changed if true, array contains different items since last call /// if false, a new item is selected, but the array /// is the same -void pum_display(pumitem_T *array, int size, int selected, bool array_changed) +/// @param cmd_startcol only for cmdline mode: column of completed match +void pum_display(pumitem_T *array, int size, int selected, bool array_changed, + int cmd_startcol) { int w; int def_width; @@ -83,7 +86,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) if (!pum_is_visible) { // To keep the code simple, we only allow changing the // draw mode when the popup menu is not being displayed - pum_external = ui_has(kUIPopupmenu); + pum_external = ui_has(kUIPopupmenu) + || (State == CMDLINE && ui_has(kUIWildmenu)); } do { @@ -95,19 +99,26 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) above_row = 0; below_row = cmdline_row; - // anchor position: the start of the completed word - row = curwin->w_wrow; - if (curwin->w_p_rl) { - col = curwin->w_width - curwin->w_wcol - 1; + // wildoptions=pum + if (State == CMDLINE) { + row = ui_has(kUICmdline) ? 0 : cmdline_row; + col = cmd_startcol; + pum_anchor_grid = ui_has(kUICmdline) ? -1 : DEFAULT_GRID_HANDLE; } else { - col = curwin->w_wcol; - } + // anchor position: the start of the completed word + row = curwin->w_wrow; + if (curwin->w_p_rl) { + col = curwin->w_width - curwin->w_wcol - 1; + } else { + col = curwin->w_wcol; + } - pum_anchor_grid = (int)curwin->w_grid.handle; - if (!ui_has(kUIMultigrid)) { - pum_anchor_grid = (int)default_grid.handle; - row += curwin->w_winrow; - col += curwin->w_wincol; + pum_anchor_grid = (int)curwin->w_grid.handle; + if (!ui_has(kUIMultigrid)) { + pum_anchor_grid = (int)default_grid.handle; + row += curwin->w_winrow; + col += curwin->w_wincol; + } } if (pum_external) { @@ -661,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. @@ -686,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; } @@ -841,3 +852,17 @@ int pum_get_height(void) { return pum_height; } + +void pum_set_boundings(dict_T *dict) +{ + if (!pum_visible()) { + return; + } + tv_dict_add_nr(dict, S_LEN("height"), pum_height); + tv_dict_add_nr(dict, S_LEN("width"), pum_width); + tv_dict_add_nr(dict, S_LEN("row"), pum_row); + tv_dict_add_nr(dict, S_LEN("col"), pum_col); + tv_dict_add_nr(dict, S_LEN("size"), pum_size); + tv_dict_add_special(dict, S_LEN("scrollbar"), + pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse); +} diff --git a/src/nvim/popupmnu.h b/src/nvim/popupmnu.h index 42e6ef5653..7956e50a93 100644 --- a/src/nvim/popupmnu.h +++ b/src/nvim/popupmnu.h @@ -1,6 +1,7 @@ #ifndef NVIM_POPUPMNU_H #define NVIM_POPUPMNU_H +#include "nvim/vim.h" #include "nvim/macros.h" #include "nvim/grid_defs.h" #include "nvim/types.h" diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ee1cf2182f..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; - } - fields->namebuf[0] = NUL; - fields->pattern[0] = NUL; - if (!qfl->qf_multiscan) { - fields->errmsg[0] = NUL; + status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields, + qfl->qf_multiline, qfl->qf_multiscan, + &tail); + if (status == QF_NOMEM) { + return status; } - 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); - } - } - if (idx == 'Z') { - qfl->qf_multiline = qfl->qf_multiignore = false; + } 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; } - - 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. @@ -1725,314 +1886,520 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) return true; } -/* - * jump to a quickfix line - * if dir == FORWARD go "errornr" valid entries forward - * if dir == BACKWARD go "errornr" valid entries backward - * if dir == FORWARD_FILE go "errornr" valid entries files backward - * if dir == BACKWARD_FILE go "errornr" valid entries files backward - * else if "errornr" is zero, redisplay the same line - * else go to entry "errornr" - */ -void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) +/// Get the next valid entry in the current quickfix/location list. The search +/// starts from the current entry. Returns NULL on failure. +static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, + int *qf_index, int dir) { - qf_info_T *ll_ref; - qfline_T *qf_ptr; - qfline_T *old_qf_ptr; - int qf_index; - int old_qf_fnum; - int old_qf_index; - int prev_index; - static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); - char_u *err = e_no_more_items; - linenr_T i; - buf_T *old_curbuf; - linenr_T old_lnum; - colnr_T screen_col; - colnr_T char_col; - char_u *line; - char_u *old_swb = p_swb; - unsigned old_swb_flags = swb_flags; - int opened_window = FALSE; - win_T *win; - win_T *altwin; - int flags; - win_T *oldwin = curwin; - int print_message = TRUE; - int len; - const bool old_KeyTyped = KeyTyped; // getting file may reset it - int ok = OK; - bool usable_win; + int idx = *qf_index; + int old_qf_fnum = qf_ptr->qf_fnum; - if (qi == NULL) - qi = &ql_info; + do { + if (idx == qi->qf_lists[qi->qf_curlist].qf_count + || qf_ptr->qf_next == NULL) { + return NULL; + } + idx++; + qf_ptr = qf_ptr->qf_next; + } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); - if (qi->qf_curlist >= qi->qf_listcount - || qi->qf_lists[qi->qf_curlist].qf_count == 0) { - EMSG(_(e_quickfix)); - return; - } + *qf_index = idx; + return qf_ptr; +} - qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; - old_qf_ptr = qf_ptr; - qf_index = qi->qf_lists[qi->qf_curlist].qf_index; - old_qf_index = qf_index; - if (dir == FORWARD || dir == FORWARD_FILE) { /* next valid entry */ - while (errornr--) { - old_qf_ptr = qf_ptr; - prev_index = qf_index; - old_qf_fnum = qf_ptr->qf_fnum; - do { - if (qf_index == qi->qf_lists[qi->qf_curlist].qf_count - || qf_ptr->qf_next == NULL) { - qf_ptr = old_qf_ptr; - qf_index = prev_index; - if (err != NULL) { - EMSG(_(err)); - goto theend; - } - errornr = 0; - break; - } - ++qf_index; - qf_ptr = qf_ptr->qf_next; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid - && !qf_ptr->qf_valid) - || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); - err = NULL; - } - } else if (dir == BACKWARD || dir == BACKWARD_FILE) { /* prev. valid entry */ - while (errornr--) { - old_qf_ptr = qf_ptr; - prev_index = qf_index; - old_qf_fnum = qf_ptr->qf_fnum; - do { - if (qf_index == 1 || qf_ptr->qf_prev == NULL) { - qf_ptr = old_qf_ptr; - qf_index = prev_index; - if (err != NULL) { - EMSG(_(err)); - goto theend; - } - errornr = 0; - break; - } - --qf_index; - qf_ptr = qf_ptr->qf_prev; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid - && !qf_ptr->qf_valid) - || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); - err = NULL; +/// Get the previous valid entry in the current quickfix/location list. The +/// search starts from the current entry. Returns NULL on failure. +static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, + int *qf_index, int dir) +{ + int idx = *qf_index; + int old_qf_fnum = qf_ptr->qf_fnum; + + do { + if (idx == 1 || qf_ptr->qf_prev == NULL) { + return NULL; } - } else if (errornr != 0) { /* go to specified number */ - while (errornr < qf_index && qf_index > 1 && qf_ptr->qf_prev != NULL) { - --qf_index; - qf_ptr = qf_ptr->qf_prev; + idx--; + qf_ptr = qf_ptr->qf_prev; + } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); + + *qf_index = idx; + return qf_ptr; +} + +/// Get the n'th (errornr) previous/next valid entry from the current entry in +/// the quickfix list. +/// dir == FORWARD or FORWARD_FILE: next valid entry +/// dir == BACKWARD or BACKWARD_FILE: previous valid entry +static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, + qfline_T *qf_ptr, int *qf_index, int dir) +{ + qfline_T *prev_qf_ptr; + int prev_index; + static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); + char_u *err = e_no_more_items; + + while (errornr--) { + prev_qf_ptr = qf_ptr; + prev_index = *qf_index; + + if (dir == FORWARD || dir == FORWARD_FILE) { + qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir); + } else { + qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir); } - while (errornr > qf_index && qf_index < - qi->qf_lists[qi->qf_curlist].qf_count - && qf_ptr->qf_next != NULL) { - ++qf_index; - qf_ptr = qf_ptr->qf_next; + + if (qf_ptr == NULL) { + qf_ptr = prev_qf_ptr; + *qf_index = prev_index; + if (err != NULL) { + EMSG(_(err)); + return NULL; + } + break; } + + err = NULL; } - qi->qf_lists[qi->qf_curlist].qf_index = qf_index; - if (qf_win_pos_update(qi, old_qf_index)) - /* No need to print the error message if it's visible in the error - * window */ - print_message = FALSE; + return qf_ptr; +} - /* - * For ":helpgrep" find a help window or open one. - */ - if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { - win_T *wp = NULL; +/// Get n'th (errornr) quickfix entry +static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, + int *cur_qfidx) +{ + int qf_idx = *cur_qfidx; - if (cmdmod.tab == 0) { - FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { - if (bt_help(wp2->w_buffer)) { - wp = wp2; - break; - } + // New error number is less than the current error number + while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) { + qf_idx--; + qf_ptr = qf_ptr->qf_prev; + } + + // New error number is greater than the current error number + while (errornr > qf_idx + && qf_idx < qi->qf_lists[qi->qf_curlist].qf_count + && qf_ptr->qf_next != NULL) { + qf_idx++; + qf_ptr = qf_ptr->qf_next; + } + + *cur_qfidx = qf_idx; + return qf_ptr; +} + +/// Find a help window or open one. +static int jump_to_help_window(qf_info_T *qi, int *opened_window) +{ + win_T *wp = NULL; + + if (cmdmod.tab != 0) { + wp = NULL; + } else { + FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { + if (bt_help(wp2->w_buffer)) { + wp = wp2; + break; } } - if (wp != NULL && wp->w_buffer->b_nwindows > 0) - win_enter(wp, true); - else { - /* - * Split off help window; put it at far top if no position - * specified, the current window is vertically split and narrow. - */ - flags = WSP_HELP; - if (cmdmod.split == 0 && curwin->w_width != Columns - && curwin->w_width < 80) - flags |= WSP_TOP; - if (qi != &ql_info) - flags |= WSP_NEWLOC; /* don't copy the location list */ - - if (win_split(0, flags) == FAIL) - goto theend; - opened_window = TRUE; /* close it when fail */ - - if (curwin->w_height < p_hh) - win_setheight((int)p_hh); - - if (qi != &ql_info) { /* not a quickfix list */ - /* The new window should use the supplied location list */ - curwin->w_llist = qi; - qi->qf_refcount++; - } + } + + if (wp != NULL && wp->w_buffer->b_nwindows > 0) { + win_enter(wp, true); + } else { + // Split off help window; put it at far top if no position + // specified, the current window is vertically split and narrow. + int flags = WSP_HELP; + if (cmdmod.split == 0 + && curwin->w_width != Columns + && curwin->w_width < 80) { + flags |= WSP_TOP; } - if (!p_im) - restart_edit = 0; /* don't want insert mode in help file */ - } + if (qi != &ql_info) { + flags |= WSP_NEWLOC; // don't copy the location list + } - /* - * If currently in the quickfix window, find another window to show the - * file in. - */ - if (bt_quickfix(curbuf) && !opened_window) { - win_T *usable_win_ptr = NULL; + if (win_split(0, flags) == FAIL) { + return FAIL; + } - /* - * If there is no file specified, we don't know where to go. - * But do advance, otherwise ":cn" gets stuck. - */ - if (qf_ptr->qf_fnum == 0) - goto theend; + *opened_window = true; - usable_win = false; + if (curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } - ll_ref = curwin->w_llist_ref; - if (ll_ref != NULL) { - /* Find a window using the same location list that is not a - * quickfix window. */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_llist == ll_ref - && wp->w_buffer->b_p_bt[0] != 'q') { - usable_win = true; - usable_win_ptr = wp; - break; - } + if (qi != &ql_info) { // not a quickfix list + // The new window should use the supplied location list + curwin->w_llist = qi; + qi->qf_refcount++; + } + } + + if (!p_im) { + restart_edit = 0; // don't want insert mode in help file + } + + return OK; +} + +/// Find a suitable window for opening a file (qf_fnum) and jump to it. +/// If the file is already opened in a window, jump to it. +static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) +{ + win_T *usable_win_ptr = NULL; + int usable_win; + int flags; + win_T *win = NULL; + win_T *altwin; + + usable_win = 0; + + qf_info_T *ll_ref = curwin->w_llist_ref; + if (ll_ref != NULL) { + // Find a window using the same location list that is not a + // quickfix window. + FOR_ALL_WINDOWS_IN_TAB(usable_win_ptr2, curtab) { + if (usable_win_ptr2->w_llist == ll_ref + && !bt_quickfix(usable_win_ptr2->w_buffer)) { + usable_win_ptr = usable_win_ptr2; + usable_win = 1; + break; } } + } - if (!usable_win) { - /* Locate a window showing a normal buffer */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_p_bt[0] == NUL) { - usable_win = true; - break; - } + if (!usable_win) { + // Locate a window showing a normal buffer + FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { + if (win2->w_buffer->b_p_bt[0] == NUL) { + win = win2; + usable_win = 1; + break; } } + } - /* - * If no usable window is found and 'switchbuf' contains "usetab" - * then search in other tabs. - */ - if (!usable_win && (swb_flags & SWB_USETAB)) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer->b_fnum == qf_ptr->qf_fnum) { - goto_tabpage_win(tp, wp); - usable_win = true; - goto win_found; - } + // If no usable window is found and 'switchbuf' contains "usetab" + // then search in other tabs. + if (!usable_win && (swb_flags & SWB_USETAB)) { + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer->b_fnum == qf_fnum) { + goto_tabpage_win(tp, wp); + usable_win = 1; + goto win_found; } } + } win_found: - /* - * If there is only one window and it is the quickfix window, create a - * new one above the quickfix window. - */ - if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { - flags = WSP_ABOVE; - if (ll_ref != NULL) - flags |= WSP_NEWLOC; - if (win_split(0, flags) == FAIL) - goto failed; /* not enough room for window */ - opened_window = TRUE; /* close it when fail */ - p_swb = empty_option; /* don't split again */ - swb_flags = 0; - RESET_BINDING(curwin); - if (ll_ref != NULL) { - /* The new window should use the location list from the - * location list window */ - curwin->w_llist = ll_ref; - ll_ref->qf_refcount++; - } - } else { - if (curwin->w_llist_ref != NULL) { - /* In a location window */ - win = usable_win_ptr; + // If there is only one window and it is the quickfix window, create a + // new one above the quickfix window. + if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) { + flags = WSP_ABOVE; + if (ll_ref != NULL) { + flags |= WSP_NEWLOC; + } + if (win_split(0, flags) == FAIL) { + return FAIL; // not enough room for window + } + *opened_window = true; // close it when fail + p_swb = empty_option; // don't split again + swb_flags = 0; + RESET_BINDING(curwin); + if (ll_ref != NULL) { + // The new window should use the location list from the + // location list window + curwin->w_llist = ll_ref; + ll_ref->qf_refcount++; + } + } else { + if (curwin->w_llist_ref != NULL) { + // In a location window + win = usable_win_ptr; + if (win == NULL) { + // Find the window showing the selected file + FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { + if (win2->w_buffer->b_fnum == qf_fnum) { + win = win2; + break; + } + } if (win == NULL) { - /* Find the window showing the selected file */ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->b_fnum == qf_ptr->qf_fnum) { - win = wp; + // Find a previous usable window + win = curwin; + do { + if (win->w_buffer->b_p_bt[0] == NUL) { break; } + if (win->w_prev == NULL) { + win = lastwin; // wrap around the top + } else { + win = win->w_prev; // go to previous window + } + } while (win != curwin); + } + } + win_goto(win); + + // If the location list for the window is not set, then set it + // to the location list from the location window + if (win->w_llist == NULL) { + win->w_llist = ll_ref; + ll_ref->qf_refcount++; + } + } else { + // Try to find a window that shows the right buffer. + // Default to the window just above the quickfix buffer. + win = curwin; + altwin = NULL; + for (;;) { + if (win->w_buffer->b_fnum == qf_fnum) { + break; + } + if (win->w_prev == NULL) { + win = lastwin; // wrap around the top + } else { + win = win->w_prev; // go to previous window + } + if (IS_QF_WINDOW(win)) { + // Didn't find it, go to the window before the quickfix window. + if (altwin != NULL) { + win = altwin; + } else if (curwin->w_prev != NULL) { + win = curwin->w_prev; + } else { + win = curwin->w_next; } - if (win == NULL) { - /* Find a previous usable window */ - win = curwin; - do { - if (win->w_buffer->b_p_bt[0] == NUL) - break; - if (win->w_prev == NULL) - win = lastwin; /* wrap around the top */ - else - win = win->w_prev; /* go to previous window */ - } while (win != curwin); - } + break; } - win_goto(win); - /* If the location list for the window is not set, then set it - * to the location list from the location window */ - if (win->w_llist == NULL) { - win->w_llist = ll_ref; - ll_ref->qf_refcount++; + // Remember a usable window. + if (altwin == NULL && !win->w_p_pvw + && win->w_buffer->b_p_bt[0] == NUL) { + altwin = win; } + } + + win_goto(win); + } + } + + return OK; +} + +/// Edit the selected file or help file. +static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, + win_T *oldwin, int *opened_window, int *abort) +{ + int retval = OK; + + if (qf_ptr->qf_type == 1) { + // Open help file (do_ecmd() will set b_help flag, readfile() will + // set b_p_ro flag). + if (!can_abandon(curbuf, forceit)) { + EMSG(_(e_nowrtmsg)); + retval = false; + } else { + retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, + ECMD_HIDE + ECMD_SET_HELP, + oldwin == curwin ? curwin : NULL); + } + } 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) { + // 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(_(e_loc_list_changed)); + } + *abort = true; + } - /* - * Try to find a window that shows the right buffer. - * Default to the window just above the quickfix buffer. - */ - win = curwin; - altwin = NULL; - for (;; ) { - if (win->w_buffer->b_fnum == qf_ptr->qf_fnum) - break; - if (win->w_prev == NULL) - win = lastwin; /* wrap around the top */ - else - win = win->w_prev; /* go to previous window */ - - if (IS_QF_WINDOW(win)) { - /* Didn't find it, go to the window before the quickfix - * window. */ - if (altwin != NULL) - win = altwin; - else if (curwin->w_prev != NULL) - win = curwin->w_prev; - else - win = curwin->w_next; + if (*abort) { + retval = false; + } + } + + return retval; +} + +/// Goto the error line in the current file using either line/column number or a +/// search pattern. +static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, + char_u *qf_pattern) +{ + linenr_T i; + char_u *line; + colnr_T screen_col; + colnr_T char_col; + + if (qf_pattern == NULL) { + // Go to line with error, unless qf_lnum is 0. + i = qf_lnum; + if (i > 0) { + if (i > curbuf->b_ml.ml_line_count) { + i = curbuf->b_ml.ml_line_count; + } + curwin->w_cursor.lnum = i; + } + if (qf_col > 0) { + curwin->w_cursor.col = qf_col - 1; + curwin->w_cursor.coladd = 0; + if (qf_viscol == true) { + // Check each character from the beginning of the error + // line up to the error column. For each tab character + // found, reduce the error column value by the length of + // a tab character. + line = get_cursor_line_ptr(); + screen_col = 0; + for (char_col = 0; char_col < curwin->w_cursor.col; char_col++) { + if (*line == NUL) { break; } - - /* Remember a usable window. */ - if (altwin == NULL && !win->w_p_pvw - && win->w_buffer->b_p_bt[0] == NUL) - altwin = win; + if (*line++ == '\t') { + curwin->w_cursor.col -= 7 - (screen_col % 8); + screen_col += 8 - (screen_col % 8); + } else { + screen_col++; + } } - - win_goto(win); } + check_cursor(); + } else { + beginline(BL_WHITE | BL_FIX); + } + } else { + // Move the cursor to the first line in the buffer + pos_T save_cursor = curwin->w_cursor; + curwin->w_cursor.lnum = 0; + if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL, NULL)) { + curwin->w_cursor = save_cursor; + } + } +} + +/// Display quickfix list index and size message +static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, + buf_T *old_curbuf, linenr_T old_lnum) +{ + // Update the screen before showing the message, unless the screen + // scrolled up. + if (!msg_scrolled) { + update_topline_redraw(); + } + snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, + qi->qf_lists[qi->qf_curlist].qf_count, + qf_ptr->qf_cleared ? _(" (line deleted)") : "", + (char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); + // Add the message, skipping leading whitespace and newlines. + int len = (int)STRLEN(IObuff); + qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); + + // Output the message. Overwrite to avoid scrolling when the 'O' + // flag is present in 'shortmess'; But when not jumping, print the + // whole message. + linenr_T i = msg_scroll; + if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) { + msg_scroll = true; + } else if (!msg_scrolled && shortmess(SHM_OVERALL)) { + msg_scroll = false; + } + msg_attr_keep(IObuff, 0, true, false); + msg_scroll = (int)i; +} + +/// jump to a quickfix line +/// if dir == FORWARD go "errornr" valid entries forward +/// if dir == BACKWARD go "errornr" valid entries backward +/// if dir == FORWARD_FILE go "errornr" valid entries files backward +/// if dir == BACKWARD_FILE go "errornr" valid entries files backward +/// else if "errornr" is zero, redisplay the same line +/// else go to entry "errornr" +void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) +{ + qfline_T *qf_ptr; + qfline_T *old_qf_ptr; + int qf_index; + int old_qf_index; + buf_T *old_curbuf; + linenr_T old_lnum; + char_u *old_swb = p_swb; + unsigned old_swb_flags = swb_flags; + int opened_window = false; + win_T *oldwin = curwin; + int print_message = true; + const bool old_KeyTyped = KeyTyped; // getting file may reset it + int retval = OK; + + if (qi == NULL) + qi = &ql_info; + + if (qi->qf_curlist >= qi->qf_listcount + || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + EMSG(_(e_quickfix)); + return; + } + + qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; + old_qf_ptr = qf_ptr; + qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + old_qf_index = qf_index; + if (dir != 0) { // next/prev valid entry + qf_ptr = get_nth_valid_entry(qi, errornr, qf_ptr, &qf_index, dir); + if (qf_ptr == NULL) { + qf_ptr = old_qf_ptr; + qf_index = old_qf_index; + goto theend; // The horror... the horror... + } + } else if (errornr != 0) { // go to specified number + qf_ptr = get_nth_entry(qi, errornr, qf_ptr, &qf_index); + } + + qi->qf_lists[qi->qf_curlist].qf_index = qf_index; + if (qf_win_pos_update(qi, old_qf_index)) + /* No need to print the error message if it's visible in the error + * window */ + print_message = FALSE; + + // For ":helpgrep" find a help window or open one. + if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) { + if (jump_to_help_window(qi, &opened_window) == FAIL) { + goto theend; + } + } + + // If currently in the quickfix window, find another window to show the + // file in. + if (bt_quickfix(curbuf) && !opened_window) { + // If there is no file specified, we don't know where to go. + // But do advance, otherwise ":cn" gets stuck. + if (qf_ptr->qf_fnum == 0) { + goto theend; + } + if (qf_jump_to_usable_window(qf_ptr->qf_fnum, &opened_window) == FAIL) { + goto failed; } } @@ -2044,122 +2411,28 @@ win_found: old_lnum = curwin->w_cursor.lnum; if (qf_ptr->qf_fnum != 0) { - if (qf_ptr->qf_type == 1) { - /* Open help file (do_ecmd() will set b_help flag, readfile() will - * set b_p_ro flag). */ - if (!can_abandon(curbuf, forceit)) { - EMSG(_(e_nowrtmsg)); - ok = false; - } else { - ok = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, - ECMD_HIDE + ECMD_SET_HELP, - oldwin == curwin ? curwin : NULL); - } - } else { - int old_qf_curlist = qi->qf_curlist; - bool is_abort = false; - - ok = 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")); - is_abort = true; - opened_window = false; - } else if (old_qf_curlist != qi->qf_curlist // -V560 - || !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")); - } - is_abort = true; - } - - if (is_abort) { - ok = false; - qi = NULL; - qf_ptr = NULL; - } + int abort = false; + retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, &opened_window, + &abort); + if (abort) { + qi = NULL; + qf_ptr = NULL; } } - if (ok == OK) { - /* When not switched to another buffer, still need to set pc mark */ - if (curbuf == old_curbuf) + if (retval == OK) { + // When not switched to another buffer, still need to set pc mark + if (curbuf == old_curbuf) { setpcmark(); - - if (qf_ptr->qf_pattern == NULL) { - /* - * Go to line with error, unless qf_lnum is 0. - */ - i = qf_ptr->qf_lnum; - if (i > 0) { - if (i > curbuf->b_ml.ml_line_count) - i = curbuf->b_ml.ml_line_count; - curwin->w_cursor.lnum = i; - } - if (qf_ptr->qf_col > 0) { - curwin->w_cursor.col = qf_ptr->qf_col - 1; - curwin->w_cursor.coladd = 0; - if (qf_ptr->qf_viscol == true) { - // Check each character from the beginning of the error - // line up to the error column. For each tab character - // found, reduce the error column value by the length of - // a tab character. - line = get_cursor_line_ptr(); - screen_col = 0; - for (char_col = 0; char_col < curwin->w_cursor.col; ++char_col) { - if (*line == NUL) - break; - if (*line++ == '\t') { - curwin->w_cursor.col -= 7 - (screen_col % 8); - screen_col += 8 - (screen_col % 8); - } else - ++screen_col; - } - } - check_cursor(); - } else - beginline(BL_WHITE | BL_FIX); - } else { - pos_T save_cursor; - - /* Move the cursor to the first line in the buffer */ - save_cursor = curwin->w_cursor; - curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', qf_ptr->qf_pattern, (long)1, - SEARCH_KEEP, NULL, NULL)) { - curwin->w_cursor = save_cursor; - } } + qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol, + qf_ptr->qf_pattern); + if ((fdo_flags & FDO_QUICKFIX) && old_KeyTyped) foldOpenCursor(); if (print_message) { - /* Update the screen before showing the message, unless the screen - * scrolled up. */ - if (!msg_scrolled) - update_topline_redraw(); - sprintf((char *)IObuff, _("(%d of %d)%s%s: "), qf_index, - qi->qf_lists[qi->qf_curlist].qf_count, - qf_ptr->qf_cleared ? _(" (line deleted)") : "", - (char *)qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); - /* Add the message, skipping leading whitespace and newlines. */ - len = (int)STRLEN(IObuff); - qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); - - /* Output the message. Overwrite to avoid scrolling when the 'O' - * flag is present in 'shortmess'; But when not jumping, print the - * whole message. */ - i = msg_scroll; - if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) { - msg_scroll = true; - } else if (!msg_scrolled && shortmess(SHM_OVERALL)) { - msg_scroll = false; - } - msg_ext_set_kind("quickfix"); - msg_attr_keep(IObuff, 0, true, false); - msg_scroll = (int)i; + qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum); } } else { if (opened_window) { @@ -2257,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) { @@ -2328,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; @@ -2397,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; @@ -2428,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 @@ -2472,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; } /* @@ -2841,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) { @@ -2972,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 { @@ -3062,51 +3346,9 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) KeyTyped = old_KeyTyped; } -/* - * Return TRUE if "buf" is the quickfix buffer. - */ -int bt_quickfix(const buf_T *const buf) -{ - return buf != NULL && buf->b_p_bt[0] == 'q'; -} - -// Return TRUE if "buf" is a "nofile", "acwrite" or "terminal" buffer. -// This means the buffer name is not a file name. -int bt_nofile(buf_T *buf) -{ - return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') - || buf->b_p_bt[0] == 'a' || buf->terminal); -} - -// Return TRUE if "buf" is a "nowrite", "nofile" or "terminal" buffer. -int bt_dontwrite(buf_T *buf) -{ - return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal); -} - -int bt_dontwrite_msg(buf_T *buf) -{ - if (bt_dontwrite(buf)) { - EMSG(_("E382: Cannot write, 'buftype' option is set")); - return TRUE; - } - return FALSE; -} - -/* - * Return TRUE if the buffer should be hidden, according to 'hidden', ":hide" - * and 'bufhidden'. - */ -int buf_hide(buf_T *buf) +static void qf_list_changed(qf_info_T *qi, int qf_idx) { - /* 'bufhidden' overrules 'hidden' and ":hide", check it first */ - switch (buf->b_p_bh[0]) { - case 'u': /* "unload" */ - case 'w': /* "wipe" */ - case 'd': return FALSE; /* "delete" */ - case 'h': return TRUE; /* "hide" */ - } - return p_hid || cmdmod.hide; + qi->qf_lists[qf_idx].qf_changedtick++; } /* @@ -3192,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 */ @@ -3546,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 { - if (au_name != NULL) - apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf); + 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 { + 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); } } @@ -3578,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; @@ -3587,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()) { @@ -3621,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) @@ -3630,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) { @@ -3681,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; @@ -3692,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]); @@ -3714,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) { @@ -3858,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); @@ -3865,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); @@ -3913,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); @@ -4112,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 ? "" @@ -4145,10 +4496,14 @@ enum { QF_GETLIST_WINID = 0x8, QF_GETLIST_CONTEXT = 0x10, QF_GETLIST_ID = 0x20, - QF_GETLIST_ALL = 0xFF + QF_GETLIST_IDX = 0x40, + QF_GETLIST_SIZE = 0x80, + 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; @@ -4186,35 +4541,85 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) return status; } -/// Return quickfix/location list details (title) as a -/// dictionary. 'what' contains the details to return. If 'list_idx' is -1, -/// then current list is used. Otherwise the specified list is used. -int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) +// Return the quickfix/location list number with the given identifier. +// Returns -1 if list is not found. +static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) { - 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); + for (int qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { + if (qi->qf_lists[qf_idx].qf_id == qfid) { + return qf_idx; + } } + return -1; +} - if (wp != NULL) { - qi = GET_LOC_LIST(wp); +/// Return the quickfix/location list window identifier in the current tabpage. +static int qf_winid(qf_info_T *qi) +{ + // 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; } - // 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; + win_T *win = qf_find_win(qi); + if (win != NULL) { + return win->handle; } + return 0; +} - int status = OK; +/// 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; + } + 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; + } + + 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) { // Use the specified quickfix/location list @@ -4223,7 +4628,7 @@ int get_errorlist_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 @@ -4231,9 +4636,8 @@ int get_errorlist_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) { @@ -4241,75 +4645,161 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (di->di_tv.v_type == VAR_NUMBER) { // For zero, use the current list or the list specifed by 'nr' if (di->di_tv.vval.v_number != 0) { - for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { - if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) { - break; - } - } - if (qf_idx == qi->qf_listcount) { - return FAIL; // List not found - } + qf_idx = qf_id2nr(qi, (unsigned)di->di_tv.vval.v_number); } - 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 ((status == OK) && (flags & QF_GETLIST_ID)) { + status = tv_dict_add_nr(retdict, S_LEN("id"), 0); + } + 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); } - if (flags & QF_GETLIST_TITLE) { + return status; +} + +/// 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)) { + 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; } @@ -4347,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"); @@ -4384,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, @@ -4395,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); @@ -4471,12 +4964,8 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, if (!newlist && (di = tv_dict_find(what, S_LEN("id"))) != NULL) { // Use the quickfix/location list with the specified id if (di->di_tv.v_type == VAR_NUMBER) { - for (qf_idx = 0; qf_idx < qi->qf_listcount; qf_idx++) { - if (qi->qf_lists[qf_idx].qf_id == di->di_tv.vval.v_number) { - break; - } - } - if (qf_idx == qi->qf_listcount) { + qf_idx = qf_id2nr(qi, (unsigned)di->di_tv.vval.v_number); + if (qf_idx == -1) { return FAIL; // List not found } } else { @@ -4508,6 +4997,13 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action, retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, title_save, action == ' ' ? 'a' : action); + if (action == 'r') { + // When replacing the quickfix list entries using + // qf_add_entries(), the title is set with a ':' prefix. + // Restore the title with the saved title. + xfree(qi->qf_lists[qf_idx].qf_title); + qi->qf_lists[qf_idx].qf_title = vim_strsave(title_save); + } xfree(title_save); } } @@ -4543,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; } @@ -4624,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; @@ -4645,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); @@ -4730,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)", @@ -4741,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 } } } @@ -4766,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"; @@ -4800,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")); @@ -4890,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, ","); @@ -4914,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; @@ -4939,6 +5447,7 @@ void ex_helpgrep(exarg_T *eap) qi->qf_curlist, NULL, // dir fnames[fi], + NULL, 0, line, lnum, @@ -4983,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 ab1a7c7b3e..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; } @@ -6963,10 +6961,11 @@ char_u *reg_submatch(int no) return NULL; } - s = reg_getline_submatch(lnum) + rsm.sm_mmatch->startpos[no].col; + s = reg_getline_submatch(lnum); if (s == NULL) { // anti-crash check, cannot happen? break; } + s += rsm.sm_mmatch->startpos[no].col; if (rsm.sm_mmatch->endpos[no].lnum == lnum) { // Within one line: take form start to end col. len = rsm.sm_mmatch->endpos[no].col - rsm.sm_mmatch->startpos[no].col; @@ -7225,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; @@ -7274,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; } @@ -7284,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. @@ -7292,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 e0e8820b87..ce7270ae65 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -4582,7 +4582,9 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos) * "pim" is NULL or contains info about a Postponed Invisible Match (start * position). */ -static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, regsubs_T *submatch, regsubs_T *m, int **listids) +static int recursive_regmatch( + nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, + regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len) { int save_reginput_col = (int)(reginput - regline); int save_reglnum = reglnum; @@ -4665,8 +4667,10 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T if (nfa_ll_index == 1) { /* Already calling nfa_regmatch() recursively. Save the lastlist[1] * values and clear them. */ - if (*listids == NULL) { + if (*listids == NULL || *listids_len < nstate) { + xfree(*listids); *listids = xmalloc(sizeof(**listids) * nstate); + *listids_len = nstate; } nfa_save_listids(prog, *listids); need_restore = TRUE; @@ -4964,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; @@ -4979,6 +4985,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, nfa_list_T *thislist; nfa_list_T *nextlist; int *listids = NULL; + int listids_len = 0; nfa_state_T *add_state; bool add_here; int add_count; @@ -5271,7 +5278,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // First try matching the invisible match, then what // follows. result = recursive_regmatch(t->state, NULL, prog, submatch, m, - &listids); + &listids, &listids_len); if (result == NFA_TOO_EXPENSIVE) { nfa_match = result; goto theend; @@ -5372,7 +5379,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // First try matching the pattern. result = recursive_regmatch(t->state, NULL, prog, submatch, m, - &listids); + &listids, &listids_len); if (result == NFA_TOO_EXPENSIVE) { nfa_match = result; goto theend; @@ -6079,8 +6086,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, fprintf(log_fd, "Postponed recursive nfa_regmatch()\n"); fprintf(log_fd, "\n"); #endif - result = recursive_regmatch(pim->state, pim, - prog, submatch, m, &listids); + result = recursive_regmatch(pim->state, pim, prog, submatch, m, + &listids, &listids_len); pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH; // for \@! and \@<! it is a match when the result is // FALSE diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 5255fd2a51..4c830bb256 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -363,7 +363,7 @@ void update_screen(int type) need_wait_return = FALSE; } - if (type >= NOT_VALID) { + if (type >= CLEAR || !default_grid.valid) { ui_comp_set_screen_valid(false); } win_ui_flush_positions(); @@ -621,6 +621,11 @@ static void win_update(win_T *wp) linenr_T mod_bot = 0; int save_got_int; + // If we can compute a change in the automatic sizing of the sign column + // under 'signcolumn=auto:X' and signs currently placed in the buffer, better + // figuring it out here so we can redraw the entire screen for it. + buf_signcols(buf); + type = wp->w_redr_type; win_grid_alloc(wp); @@ -673,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; @@ -771,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 @@ -1130,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; /* @@ -1456,7 +1464,7 @@ static void win_update(win_T *wp) set_empty_rows(wp, srow); wp->w_botline = lnum; } else { - win_draw_end(wp, '@', ' ', srow, wp->w_grid.Rows, HLF_AT); + win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, at_attr); wp->w_botline = lnum; } } else { @@ -1473,7 +1481,7 @@ static void win_update(win_T *wp) if (row + j > wp->w_grid.Rows) { j = wp->w_grid.Rows - row; } - win_draw_end(wp, i, i, row, row + (int)j, HLF_DED); + win_draw_end(wp, i, i, true, row, row + (int)j, HLF_DED); row += j; } } else if (dollar_vcol == -1) @@ -1481,12 +1489,14 @@ static void win_update(win_T *wp) // make sure the rest of the screen is blank // write the 'eob' character to rows that aren't part of the file. - win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', row, wp->w_grid.Rows, HLF_EOB); + win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.Rows, + HLF_EOB); } 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; @@ -1543,85 +1553,66 @@ int win_signcol_width(win_T *wp) return 2; } -/* - * Clear the rest of the window and mark the unused lines with "c1". use "c2" - * as the filler character. - */ -static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T hl) +/// Call grid_fill() with columns adjusted for 'rightleft' if needed. +/// Return the new offset. +static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, + int endrow, int attr) { - int n = 0; -# define FDC_OFF n - int fdc = compute_foldcolumn(wp, 0); + int nn = off + width; - int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl)); + if (nn > wp->w_grid.Columns) { + nn = wp->w_grid.Columns; + } if (wp->w_p_rl) { - // No check for cmdline window: should never be right-left. - n = fdc; + grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - nn, W_ENDCOL(wp) - off, + c1, c2, attr); + } else { + grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr); + } - if (n > 0) { - // draw the fold column at the right - if (n > wp->w_grid.Columns) { - n = wp->w_grid.Columns; - } - grid_fill(&wp->w_grid, row, endrow, wp->w_grid.Columns - n, - wp->w_grid.Columns, ' ', ' ', win_hl_attr(wp, HLF_FC)); - } + return nn; +} - if (signcolumn_on(wp)) { - int nn = n + win_signcol_width(wp); +/// Clear lines near the end of the window and mark the unused lines with "c1". +/// Use "c2" as filler character. +/// When "draw_margin" is true, then draw the sign/fold/number columns. +static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, + int endrow, hlf_T hl) +{ + int n = 0; - // draw the sign column left of the fold column - if (nn > wp->w_grid.Columns) { - nn = wp->w_grid.Columns; - } - grid_fill(&wp->w_grid, row, endrow, wp->w_grid.Columns - nn, - wp->w_grid.Columns - n, ' ', ' ', win_hl_attr(wp, HLF_SC)); - n = nn; + if (draw_margin) { + // draw the fold column + int fdc = compute_foldcolumn(wp, 0); + if (fdc > 0) { + n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow, + win_hl_attr(wp, HLF_FC)); } - - grid_fill(&wp->w_grid, row, endrow, 0, wp->w_grid.Columns - 1 - FDC_OFF, - c2, c2, attr); - grid_fill(&wp->w_grid, row, endrow, - wp->w_grid.Columns - 1 - FDC_OFF, wp->w_grid.Columns - FDC_OFF, - c1, c2, attr); - } else { - if (cmdwin_type != 0 && wp == curwin) { - /* draw the cmdline character in the leftmost column */ - n = 1; - if (n > wp->w_grid.Columns) { - n = wp->w_grid.Columns; - } - grid_fill(&wp->w_grid, row, endrow, 0, n, cmdwin_type, ' ', - win_hl_attr(wp, HLF_AT)); + // draw the sign column + int count = win_signcol_count(wp); + if (count > 0) { + n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, + endrow, win_hl_attr(wp, HLF_SC)); } - if (fdc > 0) { - int nn = n + fdc; - - // draw the fold column at the left - if (nn > wp->w_grid.Columns) { - nn = wp->w_grid.Columns; - } - grid_fill(&wp->w_grid, row, endrow, n, nn, ' ', ' ', - win_hl_attr(wp, HLF_FC)); - n = nn; + // draw the number column + if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) { + n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow, + win_hl_attr(wp, HLF_N)); } + } - if (signcolumn_on(wp)) { - int nn = n + win_signcol_width(wp); - - // draw the sign column after the fold column - if (nn > wp->w_grid.Columns) { - nn = wp->w_grid.Columns; - } - grid_fill(&wp->w_grid, row, endrow, n, nn, ' ', ' ', - win_hl_attr(wp, HLF_SC)); - n = nn; - } + int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl)); - grid_fill(&wp->w_grid, row, endrow, FDC_OFF, wp->w_grid.Columns, c1, c2, - attr); + if (wp->w_p_rl) { + grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n, + c2, c2, attr); + grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - 1 - n, W_ENDCOL(wp) - n, + c1, c2, attr); + } else { + grid_fill(&wp->w_grid, row, endrow, n, wp->w_grid.Columns, c1, c2, attr); } + set_empty_rows(wp, row); } @@ -1773,10 +1764,10 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col); // If signs are being displayed, add spaces. - if (signcolumn_on(wp)) { + if (win_signcol_count(wp) > 0) { len = wp->w_grid.Columns - col; if (len > 0) { - int len_max = win_signcol_width(wp); + int len_max = win_signcol_width(wp) * win_signcol_count(wp); if (len > len_max) { len = len_max; } @@ -2180,7 +2171,6 @@ win_line ( int vcol_off = 0; ///< offset for concealed characters int did_wcol = false; int match_conc = 0; ///< cchar for match functions - int has_match_conc = 0; ///< match wants to conceal int old_boguscols = 0; # define VCOL_HLC (vcol - vcol_off) # define FIX_FOR_BOGUSCOLS \ @@ -2202,7 +2192,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; @@ -2212,8 +2202,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; + } } } @@ -2377,31 +2369,34 @@ win_line ( filler_lines = wp->w_topfill; filler_todo = filler_lines; - // Cursor line highlighting for 'cursorline' in the current window. Not - // when Visual mode is active, because it's not clear what is selected - // then. - if (wp->w_p_cul && lnum == wp->w_cursor.lnum - && !(wp == curwin && VIsual_active)) { - int cul_attr = win_hl_attr(wp, HLF_CUL); - HlAttrs ae = syn_attr2entry(cul_attr); - - // We make a compromise here (#7383): - // * low-priority CursorLine if fg is not set - // * high-priority ("same as Vim" priority) CursorLine if fg is set - if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { - line_attr_lowprio = cul_attr; - } else { - if (!(State & INSERT) && bt_quickfix(wp->w_buffer) - && qf_current_entry(wp) == lnum) { - line_attr = hl_combine_attr(cul_attr, line_attr); + // Cursor line highlighting for 'cursorline' in the current window. + if (wp->w_p_cul && lnum == wp->w_cursor.lnum) { + // Do not show the cursor line when Visual mode is active, because it's + // not clear what is selected then. + if (!(wp == curwin && VIsual_active)) { + int cul_attr = win_hl_attr(wp, HLF_CUL); + HlAttrs ae = syn_attr2entry(cul_attr); + + // We make a compromise here (#7383): + // * low-priority CursorLine if fg is not set + // * high-priority ("same as Vim" priority) CursorLine if fg is set + if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) { + line_attr_lowprio = cul_attr; } else { - line_attr = cul_attr; + if (!(State & INSERT) && bt_quickfix(wp->w_buffer) + && qf_current_entry(wp) == lnum) { + line_attr = hl_combine_attr(cul_attr, line_attr); + } else { + line_attr = cul_attr; + } } } + // Update w_last_cursorline even if Visual mode is active. + wp->w_last_cursorline = wp->w_cursor.lnum; } // If this line has a sign with line highlighting set line_attr. - v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL); + v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1); if (v != 0) { line_attr = sign_get_attr((int)v, SIGN_LINEHL); } @@ -2450,7 +2445,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 @@ -2549,9 +2546,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); + } } } @@ -2597,6 +2595,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. @@ -2651,9 +2652,11 @@ win_line ( extra_check = true; } + int sign_idx = 0; // Repeat for the whole displayed line. for (;; ) { - has_match_conc = 0; + int has_match_conc = 0; ///< match wants to conceal + bool did_decrement_ptr = false; // Skip this quickly when working on the text. if (draw_state != WL_LINE) { if (draw_state == WL_CMDLINE - 1 && n_extra == 0) { @@ -2691,7 +2694,8 @@ win_line ( draw_state = WL_SIGN; /* Show the sign column when there are any signs in this * buffer or when using Netbeans. */ - if (signcolumn_on(wp)) { + int count = win_signcol_count(wp); + if (count > 0) { int text_sign; // Draw cells with the sign value or blank. c_extra = ' '; @@ -2700,7 +2704,8 @@ win_line ( n_extra = win_signcol_width(wp); if (row == startrow + filler_lines && filler_todo <= 0) { - text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT); + text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT, + sign_idx, count); if (text_sign != 0) { p_extra = sign_get_text(text_sign); int symbol_blen = (int)STRLEN(p_extra); @@ -2718,6 +2723,11 @@ win_line ( char_attr = sign_get_attr(text_sign, SIGN_TEXT); } } + + sign_idx++; + if (sign_idx < count) { + draw_state = WL_SIGN - 1; + } } } @@ -2754,8 +2764,15 @@ win_line ( if (wp->w_skipcol > 0) for (p_extra = extra; *p_extra == ' '; ++p_extra) *p_extra = '-'; - if (wp->w_p_rl) /* reverse line numbers */ - rl_mirror(extra); + if (wp->w_p_rl) { // reverse line numbers + // like rl_mirror(), but keep the space at the end + char_u *p2 = skiptowhite(extra) - 1; + for (char_u *p1 = extra; p1 < p2; p1++, p2--) { + const int t = *p1; + *p1 = *p2; + *p2 = t; + } + } p_extra = extra; c_extra = NUL; c_final = NUL; @@ -2766,7 +2783,8 @@ win_line ( n_extra = number_width(wp) + 1; char_attr = win_hl_attr(wp, HLF_N); - int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL); + int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL, + 0, 1); if (num_sign != 0) { // :sign defined with "numhl" highlight. char_attr = sign_get_attr(num_sign, SIGN_NUMHL); @@ -2853,6 +2871,7 @@ win_line ( } if (draw_state == WL_LINE - 1 && n_extra == 0) { + sign_idx = 0; draw_state = WL_LINE; if (saved_n_extra) { /* Continue item from end of wrapped line. */ @@ -2936,8 +2955,11 @@ win_line ( shl->endcol = tmp_col; } shl->attr_cur = shl->attr; - if (cur != NULL && syn_name2id((char_u *)"Conceal") - == cur->hlg_id) { + // Match with the "Conceal" group results in hiding + // the match. + if (cur != NULL + && shl != &search_hl + && syn_name2id((char_u *)"Conceal") == cur->hlg_id) { has_match_conc = v == (long)shl->startcol ? 2 : 1; match_conc = cur->conceal_char; } else { @@ -3207,6 +3229,7 @@ win_line ( // Put pointer back so that the character will be // displayed at the start of the next line. ptr--; + did_decrement_ptr = true; } else if (*ptr != NUL) { ptr += mb_l - 1; } @@ -3255,16 +3278,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; } @@ -3356,7 +3381,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. @@ -3663,6 +3688,11 @@ win_line ( prev_syntax_id = 0; is_concealing = FALSE; } + + if (n_skip > 0 && did_decrement_ptr) { + // not showing the '>', put pointer back to avoid getting stuck + ptr++; + } } /* In the cursor line and we may be concealing characters: correct @@ -3971,8 +4001,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) @@ -4192,11 +4224,9 @@ win_line ( ) || lcs_eol_one == -1) break; - /* When the window is too narrow draw all "@" lines. */ - if (draw_state != WL_LINE - && filler_todo <= 0 - ) { - win_draw_end(wp, '@', ' ', row, wp->w_grid.Rows, HLF_AT); + // When the window is too narrow draw all "@" lines. + if (draw_state != WL_LINE && filler_todo <= 0) { + win_draw_end(wp, '@', ' ', true, row, wp->w_grid.Rows, HLF_AT); row = endrow; } @@ -4813,13 +4843,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); } @@ -6351,14 +6381,12 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } -/* - * show the current mode and ruler - * - * If clear_cmdline is TRUE, clear the rest of the cmdline. - * If clear_cmdline is FALSE there may be a message there that needs to be - * cleared only if a mode is shown. - * Return the length of the message (0 if no message). - */ +// Show the current mode and ruler. +// +// If clear_cmdline is TRUE, clear the rest of the cmdline. +// If clear_cmdline is FALSE there may be a message there that needs to be +// cleared only if a mode is shown. +// Return the length of the message (0 if no message). int showmode(void) { int need_clear; @@ -7151,8 +7179,12 @@ void screen_resize(int width, int height) if (curwin->w_p_scb) do_check_scrollbind(TRUE); if (State & CMDLINE) { + redraw_popupmenu = false; update_screen(NOT_VALID); redrawcmdline(); + if (pum_drawn()) { + cmdline_pum_display(false); + } } else { update_topline(); if (pum_drawn()) { diff --git a/src/nvim/search.c b/src/nvim/search.c index 777ea07a21..6e00602e66 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -310,7 +310,7 @@ void free_search_patterns(void) /// Save and restore the search pattern for incremental highlight search /// feature. /// -/// It's similar but different from save_search_patterns() and +/// It's similar to but different from save_search_patterns() and /// restore_search_patterns(), because the search pattern must be restored when /// cancelling incremental searching even if it's called inside user functions. void save_last_search_pattern(void) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 8864301e4c..4440d3905f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -856,13 +856,13 @@ static int msgpack_sd_writer_write(void *data, const char *buf, size_t len) return 0; } -/// Check whether writing to shada file was disabled with -i NONE +/// Check whether writing to shada file was disabled ("-i NONE" or "--clean"). /// /// @return true if it was disabled, false otherwise. static bool shada_disabled(void) FUNC_ATTR_PURE { - return used_shada_file != NULL && STRCMP(used_shada_file, "NONE") == 0; + return strequal(p_shadafile, "NONE"); } /// Read ShaDa file @@ -1542,14 +1542,14 @@ 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 (used_shada_file != NULL) { - file = used_shada_file; + if (p_shadafile != NULL && *p_shadafile != NUL) { + file = p_shadafile; } else { if ((file = find_shada_parameter('n')) == NULL || *file == NUL) { file = shada_get_default_file(); } // XXX It used to be one level lower, so that whatever is in - // `used_shada_file` was expanded. I intentionally moved it here + // `p_shadafile` was expanded. I intentionally moved it here // because various expansions must have already be done by the shell. // If shell is not performing them then they should be done in main.c // where arguments are parsed, *not here*. @@ -2739,8 +2739,8 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize jump list const void *jump_iter = NULL; + cleanup_jumplist(curwin, false); setpcmark(); - cleanup_jumplist(); do { xfmark_T fm; jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 4443fd8a2e..b4f2709d45 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -13,6 +13,7 @@ struct signlist linenr_T lnum; // line number which has this sign int typenr; // typenr of sign signlist_T *next; // next signlist entry + signlist_T *prev; // previous entry -- for easy reordering }; // type argument for buf_getsigntype() and sign_get_attr() diff --git a/src/nvim/spell.c b/src/nvim/spell.c index ec4da88ea1..0fc33bec81 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -2294,7 +2294,7 @@ static void use_midword(slang_T *lp, win_T *wp) } // Find the region "region[2]" in "rp" (points to "sl_regions"). -// Each region is simply stored as the two characters of it's name. +// Each region is simply stored as the two characters of its name. // Returns the index if found (first is 0), REGION_ALL if not found. static int find_region(char_u *rp, char_u *region) { @@ -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 7a6f2fce39..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); @@ -3227,7 +3228,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist prevp = p; if (get_affitem(affile->af_flagtype, &p) != 0) { // A flag is a postponed prefix flag if it appears in "af_pref" - // and it's ID is not zero. + // and its ID is not zero. STRLCPY(key, prevp, p - prevp + 1); hi = hash_find(&affile->af_pref, key); if (!HASHITEM_EMPTY(hi)) { @@ -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/strings.c b/src/nvim/strings.c index 96a8dfd295..3ba9354c67 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -678,12 +678,12 @@ static float_T tv_float(typval_T *const tvs, int *const idxp) // are discarded. If "str_m" is greater than zero it is guaranteed // the resulting string will be NUL-terminated. -// vim_vsnprintf() can be invoked with either "va_list" or a list of +// vim_vsnprintf_typval() can be invoked with either "va_list" or a list of // "typval_T". When the latter is not used it must be NULL. /// Append a formatted value to the string /// -/// @see vim_vsnprintf(). +/// @see vim_vsnprintf_typval(). int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...) FUNC_ATTR_PRINTF(3, 4) { @@ -697,7 +697,7 @@ int vim_snprintf_add(char *str, size_t str_m, char *fmt, ...) } va_list ap; va_start(ap, fmt); - const int str_l = vim_vsnprintf(str + len, space, fmt, ap, NULL); + const int str_l = vim_vsnprintf(str + len, space, fmt, ap); va_end(ap); return str_l; } @@ -715,7 +715,7 @@ int vim_snprintf(char *str, size_t str_m, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - const int str_l = vim_vsnprintf(str, str_m, fmt, ap, NULL); + const int str_l = vim_vsnprintf(str, str_m, fmt, ap); va_end(ap); return str_l; } @@ -736,6 +736,10 @@ static const char *infinity_str(bool positive, char fmt_spec, return table[idx]; } +int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) +{ + return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL); +} /// Write formatted value to the string /// @@ -748,8 +752,8 @@ static const char *infinity_str(bool positive, char fmt_spec, /// /// @return Number of bytes excluding NUL byte that would be written to the /// string if str_m was greater or equal to the return value. -int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, - typval_T *const tvs) +int vim_vsnprintf_typval( + char *str, size_t str_m, const char *fmt, va_list ap, typval_T *const tvs) { size_t str_l = 0; bool str_avail = str_l < str_m; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 0104f0d834..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) { @@ -5956,6 +5970,7 @@ static const char *highlight_init_both[] = { "default link Substitute Search", "default link Whitespace NonText", "default link MsgSeparator StatusLine", + "default link NormalFloat Pmenu", NULL }; @@ -7445,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 410b9dfcbd..81af23f911 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -436,11 +436,15 @@ do_tag ( tagmatchname = vim_strsave(name); } - if (type == DT_TAG || type == DT_SELECT || type == DT_JUMP + if (type == DT_SELECT || type == DT_JUMP || type == DT_LTAG) { cur_match = MAXCOL - 1; } - max_num_matches = cur_match + 1; + if (type == DT_TAG) { + max_num_matches = MAXCOL; + } else { + max_num_matches = cur_match + 1; + } /* when the argument starts with '/', use it as a regexp */ if (!no_regexp && *name == '/') { @@ -495,7 +499,7 @@ do_tag ( if (type == DT_CSCOPE && num_matches > 1) { cs_print_tags(); ask_for_selection = true; - } else if (type == DT_TAG) { + } else if (type == DT_TAG && *tag != NUL) { // If a count is supplied to the ":tag <name>" command, then // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; @@ -1523,58 +1527,38 @@ line_read_in: } parse_line: - /* - * Figure out where the different strings are in this line. - * For "normal" tags: Do a quick check if the tag matches. - * This speeds up tag searching a lot! - */ - if (orgpat.headlen - ) { + // When the line is too long the NUL will not be in the + // last-but-one byte (see vim_fgets()). + // Has been reported for Mozilla JS with extremely long names. + // In that case we can't parse it and we ignore the line. + if (lbuf[LSIZE - 2] != NUL && !use_cscope) { + if (p_verbose >= 5) { + verbose_enter(); + MSG(_("Ignoring long line in tags file")); + verbose_leave(); + } + if (state != TS_LINEAR) { + // Avoid getting stuck. + linear = true; + state = TS_LINEAR; + vim_fseek(fp, search_info.low_offset, SEEK_SET); + } + continue; + } + + // Figure out where the different strings are in this line. + // For "normal" tags: Do a quick check if the tag matches. + // This speeds up tag searching a lot! + if (orgpat.headlen) { tagp.tagname = lbuf; tagp.tagname_end = vim_strchr(lbuf, TAB); - if (tagp.tagname_end == NULL) - { - if (vim_strchr(lbuf, NL) == NULL) { - /* Truncated line, ignore it. Has been reported for - * Mozilla JS with extremely long names. */ - if (p_verbose >= 5) { - verbose_enter(); - MSG(_("Ignoring long line in tags file")); - verbose_leave(); - } - if (state != TS_LINEAR) { - /* Avoid getting stuck. */ - linear = TRUE; - state = TS_LINEAR; - vim_fseek(fp, search_info.low_offset, SEEK_SET); - } - continue; - } - - /* Corrupted tag line. */ - line_error = TRUE; + if (tagp.tagname_end == NULL) { + // Corrupted tag line. + line_error = true; break; } /* - * Check for old style static tag: "file:tag file .." - */ - tagp.fname = NULL; - for (p = lbuf; p < tagp.tagname_end; ++p) { - if (*p == ':') { - if (tagp.fname == NULL) - tagp.fname = tagp.tagname_end + 1; - if ( fnamencmp(lbuf, tagp.fname, p - lbuf) == 0 - && tagp.fname[p - lbuf] == TAB - ) { - /* found one */ - tagp.tagname = p + 1; - break; - } - } - } - - /* * Skip this line if the length of the tag is different and * there is no regexp, or the tag is too short. */ @@ -1673,11 +1657,8 @@ parse_line: if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) continue; - /* - * Can be a matching tag, isolate the file name and command. - */ - if (tagp.fname == NULL) - tagp.fname = tagp.tagname_end + 1; + // Can be a matching tag, isolate the file name and command. + tagp.fname = tagp.tagname_end + 1; tagp.fname_end = vim_strchr(tagp.fname, TAB); tagp.command = tagp.fname_end + 1; if (tagp.fname_end == NULL) @@ -1744,19 +1725,12 @@ parse_line: /* Don't change the ordering, always use the same table. */ mtt = MT_GL_OTH; } else { - /* Decide in which array to store this match. */ - is_current = test_for_current( - tagp.fname, tagp.fname_end, tag_fname, - buf_ffname); - { - if (tagp.tagname != lbuf) - is_static = TRUE; /* detected static tag before */ - else - is_static = test_for_static(&tagp); - } + // Decide in which array to store this match. + is_current = test_for_current(tagp.fname, tagp.fname_end, tag_fname, + buf_ffname); + is_static = test_for_static(&tagp); - /* decide in which of the sixteen tables to store this - * match */ + // Decide in which of the sixteen tables to store this match. if (is_static) { if (is_current) mtt = MT_ST_CUR; @@ -1858,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 { @@ -1996,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) @@ -2054,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; } @@ -2188,25 +2179,9 @@ parse_tag_line ( */ static bool test_for_static(tagptrs_T *tagp) { - char_u *p; + char_u *p; - int len; - - /* - * Check for old style static tag: "file:tag file .." - */ - len = (int)(tagp->fname_end - tagp->fname); - p = tagp->tagname + len; - if ( p < tagp->tagname_end - && *p == ':' - && fnamencmp(tagp->tagname, tagp->fname, len) == 0) { - tagp->tagname = p + 1; - return TRUE; - } - - /* - * Check for new style static tag ":...<Tab>file:[<Tab>...]" - */ + // Check for new style static tag ":...<Tab>file:[<Tab>...]" p = tagp->command; while ((p = vim_strchr(p, '\t')) != NULL) { ++p; @@ -2905,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 ffe650f416..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 @@ -400,9 +401,9 @@ void terminal_enter(void) showmode(); curwin->w_redr_status = true; // For mode() in statusline. #8323 ui_busy_start(); - redraw(false); s->state.execute = terminal_execute; + s->state.check = terminal_check; state_enter(&s->state); restart_edit = 0; @@ -415,8 +416,10 @@ void terminal_enter(void) // draw the unfocused cursor invalidate_terminal(s->term, s->term->cursor.row, s->term->cursor.row + 1); + if (curbuf->terminal == s->term && !s->close) { + terminal_check_cursor(); + } unshowmode(true); - redraw(curbuf->handle != s->term->buf_handle); ui_busy_stop(); if (s->close) { bool wipe = s->term->buf_handle != 0; @@ -427,6 +430,45 @@ void terminal_enter(void) } } +static void terminal_check_cursor(void) +{ + Terminal *term = curbuf->terminal; + curwin->w_wrow = term->cursor.row; + curwin->w_wcol = term->cursor.col + win_col_off(curwin); + curwin->w_cursor.lnum = MIN(curbuf->b_ml.ml_line_count, + row_to_linenr(term, term->cursor.row)); + // Nudge cursor when returning to normal-mode. + int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1); + curwin->w_cursor.col = MAX(0, term->cursor.col + win_col_off(curwin) + off); + curwin->w_cursor.coladd = 0; + mb_check_adjust_col(curwin); +} + +// Function executed before each iteration of terminal mode. +// Return: +// 1 if the iteration should continue normally +// 0 if the main loop must exit +static int terminal_check(VimState *state) +{ + if (stop_insert_mode) { + return 0; + } + + terminal_check_cursor(); + + if (must_redraw) { + update_screen(0); + } + + if (need_maketitle) { // Update title in terminal-mode. #7248 + maketitle(); + } + + setcursor(); + ui_flush(); + return 1; +} + static int terminal_execute(VimState *state, int key) { TerminalState *s = (TerminalState *)state; @@ -878,15 +920,20 @@ static VTermKey convert_key(int key, VTermModifier *statep) case K_KINS: return VTERM_KEY_KP_0; case K_K1: FALLTHROUGH; case K_KEND: return VTERM_KEY_KP_1; - case K_K2: return VTERM_KEY_KP_2; + case K_K2: FALLTHROUGH; + case K_KDOWN: return VTERM_KEY_KP_2; case K_K3: FALLTHROUGH; case K_KPAGEDOWN: return VTERM_KEY_KP_3; - case K_K4: return VTERM_KEY_KP_4; - case K_K5: return VTERM_KEY_KP_5; - case K_K6: return VTERM_KEY_KP_6; + case K_K4: FALLTHROUGH; + case K_KLEFT: return VTERM_KEY_KP_4; + case K_K5: FALLTHROUGH; + case K_KORIGIN: return VTERM_KEY_KP_5; + case K_K6: FALLTHROUGH; + case K_KRIGHT: return VTERM_KEY_KP_6; case K_K7: FALLTHROUGH; case K_KHOME: return VTERM_KEY_KP_7; - case K_K8: return VTERM_KEY_KP_8; + case K_K8: FALLTHROUGH; + case K_KUP: return VTERM_KEY_KP_8; case K_K9: FALLTHROUGH; case K_KPAGEUP: return VTERM_KEY_KP_9; case K_KDEL: FALLTHROUGH; @@ -977,6 +1024,9 @@ static bool send_mouse_event(Terminal *term, int c) { int row = mouse_row, col = mouse_col, grid = mouse_grid; win_T *mouse_win = mouse_find_win(&grid, &row, &col); + if (mouse_win == NULL) { + goto end; + } if (term->forward_mouse && mouse_win->w_buffer->terminal == term) { // event in the terminal window and mouse events was enabled by the @@ -1024,6 +1074,7 @@ static bool send_mouse_event(Terminal *term, int c) return mouse_win == curwin; } +end: ins_char_typebuf(c); return true; } @@ -1128,11 +1179,7 @@ static void refresh_terminal(Terminal *term) static void refresh_timer_cb(TimeWatcher *watcher, void *data) { refresh_pending = false; - if (exiting // Cannot redraw (requires event loop) during teardown/exit. - || (State & CMDPREVIEW) - // WM_LIST (^D) is not redrawn, unlike the normal wildmenu. So we must - // skip redraws to keep it visible. - || wild_menu_showing == WM_LIST) { + if (exiting) { // Cannot redraw (requires event loop) during teardown/exit. return; } Terminal *term; @@ -1142,12 +1189,8 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) map_foreach(invalidated_terminals, term, stub, { refresh_terminal(term); }); - bool any_visible = is_term_visible(); pmap_clear(ptr_t)(invalidated_terminals); unblock_autocmds(); - if (any_visible) { - redraw(true); - } } static void refresh_size(Terminal *term, buf_T *buf) @@ -1261,61 +1304,6 @@ static void refresh_screen(Terminal *term, buf_T *buf) term->invalid_end = -1; } -/// @return true if any invalidated terminal buffer is visible to the user -static bool is_term_visible(void) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer->terminal - && pmap_has(ptr_t)(invalidated_terminals, wp->w_buffer->terminal)) { - return true; - } - } - return false; -} - -static void redraw(bool restore_cursor) -{ - Terminal *term = curbuf->terminal; - if (!term) { - restore_cursor = true; - } - - int save_row = 0; - int save_col = 0; - if (restore_cursor) { - // save the current row/col to restore after updating screen when not - // focused - save_row = ui_current_row(); - save_col = ui_current_col(); - } - block_autocmds(); - - if (must_redraw) { - update_screen(0); - } - - if (need_maketitle) { // Update title in terminal-mode. #7248 - maketitle(); - } - - if (restore_cursor) { - ui_cursor_goto(save_row, save_col); - } else if (term) { - curwin->w_wrow = term->cursor.row; - curwin->w_wcol = term->cursor.col + win_col_off(curwin); - curwin->w_cursor.lnum = MIN(curbuf->b_ml.ml_line_count, - row_to_linenr(term, term->cursor.row)); - // Nudge cursor when returning to normal-mode. - int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1); - curwin->w_cursor.col = MAX(0, term->cursor.col + win_col_off(curwin) + off); - curwin->w_cursor.coladd = 0; - mb_check_adjust_col(curwin); - } - - unblock_autocmds(); - ui_flush(); -} - static void adjust_topline(Terminal *term, buf_T *buf, long added) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -1351,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..7b1f0f59cc 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -14,8 +14,6 @@ export NVIM_PRG := $(NVIM_PRG) export TMPDIR := $(abspath ../../../Xtest-tmpdir) SCRIPTS_DEFAULT = \ - test14.out \ - test37.out \ test42.out \ test48.out \ test52.out \ @@ -23,7 +21,6 @@ SCRIPTS_DEFAULT = \ ifneq ($(OS),Windows_NT) SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ - test17.out \ test49.out \ endif @@ -50,8 +47,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 +78,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 +95,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 +157,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/load.vim b/src/nvim/testdir/load.vim index 2e01338dd0..6369b8f45e 100644 --- a/src/nvim/testdir/load.vim +++ b/src/nvim/testdir/load.vim @@ -1,3 +1,5 @@ +" Also used by: test/functional/helpers.lua + function! s:load_factor() abort let timeout = 200 let times = [] @@ -23,8 +25,8 @@ function! s:load_factor() abort endfunction " Compute load factor only once. -let s:load_factor = s:load_factor() +let g:test_load_factor = s:load_factor() function! LoadAdjust(num) abort - return float2nr(ceil(a:num * s:load_factor)) + return float2nr(ceil(a:num * g:test_load_factor)) endfunction 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/test37.in b/src/nvim/testdir/test37.in deleted file mode 100644 index 156bf74a10..0000000000 --- a/src/nvim/testdir/test37.in +++ /dev/null @@ -1,116 +0,0 @@ -Test for 'scrollbind'. <eralston@computer.org> Do not add a line below! -STARTTEST -: -:set noscrollbind -:set scrollopt=ver,jump -:set scrolloff=2 -:set nowrap -:set noequalalways -:set splitbelow -:" TEST using two windows open to one buffer, one extra empty window -:split -:new -t: -:resize 8 -/^start of window 1$/ -zt: -:set scrollbind -j: -:resize 7 -/^start of window 2$/ -zt: -:set scrollbind -:" -- start of tests -- -:" TEST scrolling down -L5jHyybpr0tHyybpr1tL6jHyybpr2kHyybpr3: -:" TEST scrolling up -tH4kjHtHyybpr4kHyybpr5k3ktHjHyybpr6tHyybpr7: -:" TEST horizontal scrolling -:set scrollopt+=hor -gg"zyyG"zpGt015zly$bp"zpGky$bp"zpG: -k10jH7zhg0y$bp"zpGtHg0y$bp"zpG: -:set scrollopt-=hor -:" ****** tests using two different buffers ***** -tj: -:close -t: -:set noscrollbind -:/^start of window 2$/,/^end of window 2$/y -:new -tj4"zpGp: -t/^start of window 1$/ -zt: -:set scrollbind -j: -/^start of window 2$/ -zt: -:set scrollbind -:" -- start of tests -- -:" TEST scrolling down -L5jHyybpr0tHyybpr1tL6jHyybpr2kHyybpr3: -:" TEST scrolling up -tH4kjHtHyybpr4kHyybpr5k3ktHjHyybpr6tHyybpr7: -:" TEST horizontal scrolling -:set scrollopt+=hor -gg"zyyG"zpGt015zly$bp"zpGky$bp"zpG: -k10jH7zhg0y$bp"zpGtHg0y$bp"zpG: -:set scrollopt-=hor -:" TEST syncbind -t:set noscb -ggLj:set noscb -ggL:set scb -t:set scb -GjG:syncbind -HktHjHyybptyybp: -t:set noscb -ggLj:set noscb -ggL:set scb -t:set scb -tGjGt:syncbind -HkjHtHyybptjyybp: -tH3kjHtHyybptjyybp: -:" ***** done with tests ***** -:w! test.out " Write contents of this file -:qa! -ENDTEST - - -start of window 1 -. line 01 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 01 -. line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02 -. line 03 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 03 -. line 04 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 04 -. line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05 -. line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06 -. line 07 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 07 -. line 08 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 08 -. line 09 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 09 -. line 10 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 10 -. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11 -. line 12 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 12 -. line 13 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 13 -. line 14 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 14 -. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15 -end of window 1 - - -start of window 2 -. line 01 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 01 -. line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02 -. line 03 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 03 -. line 04 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 04 -. line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05 -. line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06 -. line 07 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 07 -. line 08 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 08 -. line 09 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 09 -. line 10 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 10 -. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11 -. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12 -. line 13 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 13 -. line 14 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 14 -. line 15 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 15 -. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16 -end of window 2 - -end of test37.in (please don't delete this line) diff --git a/src/nvim/testdir/test37.ok b/src/nvim/testdir/test37.ok deleted file mode 100644 index d0b74853b3..0000000000 --- a/src/nvim/testdir/test37.ok +++ /dev/null @@ -1,33 +0,0 @@ - -0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05 -1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05 -2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11 -3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11 -4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06 -5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06 -6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02 -7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02 -56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02 -UTSRQPONMLKJIHGREDCBA9876543210 02 -. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11 -. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11 - -0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05 -1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05 -2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11 -3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11 -4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06 -5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06 -6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02 -7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02 -56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02 -UTSRQPONMLKJIHGREDCBA9876543210 02 -. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11 -. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11 - -. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16 -:set scrollbind -:set scrollbind -. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16 -j: -. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12 diff --git a/src/nvim/testdir/test50.in b/src/nvim/testdir/test50.in deleted file mode 100644 index 392177b808..0000000000 --- a/src/nvim/testdir/test50.in +++ /dev/null @@ -1,89 +0,0 @@ -Test for shortpathname ':8' extension. -Only for use on Win32 systems! - -STARTTEST -:fun! TestIt(file, bits, expected) - let res=fnamemodify(a:file,a:bits) - if a:expected == '' - echo "'".a:file."'->(".a:bits.")->'".res."'" - else - if substitute(res,'/','\\', 'g') != substitute( a:expected, '/','\\', 'g') - echo "FAILED: '".a:file."'->(".a:bits.")->'".res."'" - echo "Expected: '".a:expected."'" - else - echo "OK" - endif - endif -endfun -:fun! MakeDir( dirname ) - "exe '!mkdir '.substitute(a:dirname,'/','\\','g') - call system('mkdir '.substitute(a:dirname,'/','\\','g')) -endfun -:fun! RMDir( dirname) - "exe '!rmdir '.substitute(a:dirname,'/','\\','g') - call system('rmdir '.substitute(a:dirname,'/','\\','g')) -endfun -:fun! MakeFile( filename) - "exe '!copy nul '.substitute(a:filename,'/','\\','g') - call system('copy nul '.substitute(a:filename,'/','\\','g')) -endfun -:fun! TestColonEight() - redir! >test.out - " This could change for CygWin to //cygdrive/c - let dir1='c:/x.x.y' - if filereadable(dir1) || isdirectory(dir1) - echo "FATAL: '".dir1."' exists, cannot run test" - return - endif - let file1=dir1.'/zz.y.txt' - let nofile1=dir1.'/z.y.txt' - let dir2=dir1.'/VimIsTheGreatestSinceSlicedBread' - let file2=dir2.'/z.txt' - let nofile2=dir2.'/zz.txt' - call MakeDir( dir1 ) - let resdir1 = substitute(fnamemodify(dir1, ':p:8'), '\\$', '', '') - if resdir1 !~ '\V\^c:/XX\x\x\x\x~1.Y\$' - echo "FATAL: unexpected short name: " . resdir1 - echo "INFO: please report your OS to vim-dev" - return - endif - let resfile1=resdir1.'/ZZY~1.TXT' - let resnofile1=resdir1.'/z.y.txt' - let resdir2=resdir1.'/VIMIST~1' - let resfile2=resdir2.'/z.txt' - let resnofile2=resdir2.'/zz.txt' - call MakeDir( dir2 ) - call MakeFile( file1 ) - call MakeFile( file2 ) - call TestIt(file1, ':p:8', resfile1) - call TestIt(nofile1, ':p:8', resnofile1) - call TestIt(file2, ':p:8', resfile2) - call TestIt(nofile2, ':p:8', resnofile2) - call TestIt(nofile2, ':p:8:h', fnamemodify(resnofile2,':h')) - exe 'cd '.dir1 - call TestIt(file1, ':.:8', strpart(resfile1,strlen(resdir1)+1)) - call TestIt(nofile1, ':.:8', strpart(resnofile1,strlen(resdir1)+1)) - call TestIt(file2, ':.:8', strpart(resfile2,strlen(resdir1)+1)) - call TestIt(nofile2, ':.:8', strpart(resnofile2,strlen(resdir1)+1)) - let $HOME=dir1 - call TestIt(file1, ':~:8', '~'.strpart(resfile1,strlen(resdir1))) - call TestIt(nofile1, ':~:8', '~'.strpart(resnofile1,strlen(resdir1))) - call TestIt(file2, ':~:8', '~'.strpart(resfile2,strlen(resdir1))) - call TestIt(nofile2, ':~:8', '~'.strpart(resnofile2,strlen(resdir1))) - cd c:/ - call delete( file2 ) - call delete( file1 ) - call RMDir( dir2 ) - call RMDir( dir1 ) - echo - redir END -endfun -:let dir = getcwd() -:call TestColonEight() -:exe "cd " . dir -:edit! test.out -:set ff=dos -:w -:qa! -ENDTEST - diff --git a/src/nvim/testdir/test50.ok b/src/nvim/testdir/test50.ok deleted file mode 100644 index 91ef1d6604..0000000000 --- a/src/nvim/testdir/test50.ok +++ /dev/null @@ -1,14 +0,0 @@ - -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK -OK diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 368fc9810d..3a9ffbdbf3 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -240,13 +240,53 @@ func Test_arglistid() call assert_equal(0, arglistid()) endfunc -" Test for argv() +" Tests for argv() and argc() func Test_argv() call Reset_arglist() call assert_equal([], argv()) call assert_equal("", argv(2)) + call assert_equal(0, argc()) argadd a b c d + call assert_equal(4, argc()) call assert_equal('c', argv(2)) + + let w1_id = win_getid() + split + let w2_id = win_getid() + arglocal + args e f g + tabnew + let w3_id = win_getid() + split + let w4_id = win_getid() + argglobal + tabfirst + call assert_equal(4, argc(w1_id)) + call assert_equal('b', argv(1, w1_id)) + call assert_equal(['a', 'b', 'c', 'd'], argv(-1, w1_id)) + + call assert_equal(3, argc(w2_id)) + call assert_equal('f', argv(1, w2_id)) + call assert_equal(['e', 'f', 'g'], argv(-1, w2_id)) + + call assert_equal(3, argc(w3_id)) + call assert_equal('e', argv(0, w3_id)) + call assert_equal(['e', 'f', 'g'], argv(-1, w3_id)) + + call assert_equal(4, argc(w4_id)) + call assert_equal('c', argv(2, w4_id)) + call assert_equal(['a', 'b', 'c', 'd'], argv(-1, w4_id)) + + call assert_equal(4, argc(-1)) + call assert_equal(3, argc()) + call assert_equal('d', argv(3, -1)) + call assert_equal(['a', 'b', 'c', 'd'], argv(-1, -1)) + tabonly | only | enew! + " Negative test cases + call assert_equal(-1, argc(100)) + call assert_equal('', argv(1, 100)) + call assert_equal([], argv(-1, 100)) + call assert_equal('', argv(10, -1)) endfunc " Test for the :argedit command @@ -289,6 +329,18 @@ func Test_argedit() %argd bwipe! C bwipe! D + + " :argedit reuses the current buffer if it is empty + %argd + " make sure to use a new buffer number for x when it is loaded + bw! x + new + let a = bufnr('') + argedit x + call assert_equal(a, bufnr('')) + call assert_equal('x', bufname('')) + %argd + bw! x endfunc " Test for the :argdelete command diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index f1fb8e67b9..8182c6973b 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,10 +68,35 @@ 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 -endif + + 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 + +endif "has('timers') func Test_bufunload() augroup test_bufunload_group @@ -457,7 +504,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 +645,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 @@ -1161,23 +1208,23 @@ func Test_TextYankPost() norm "ayiw call assert_equal( - \{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, \g:event) norm y_ call assert_equal( - \{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'}, + \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'regtype': 'V'}, \g:event) call feedkeys("\<C-V>y", 'x') call assert_equal( - \{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"}, + \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'regtype': "\x161"}, \g:event) norm "xciwbar call assert_equal( - \{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, + \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, \g:event) norm "bdiw call assert_equal( - \{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, + \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, \g:event) call assert_equal({}, v:event) @@ -1308,3 +1355,319 @@ func Test_Changed_FirstTime() call delete('Xchanged.txt') bwipe! endfunc + +func Test_autocmd_nested() + let g:did_nested = 0 + augroup Testing + au WinNew * edit somefile + au BufNew * let g:did_nested = 1 + augroup END + split + call assert_equal(0, g:did_nested) + close + bwipe! somefile + + " old nested argument still works + augroup Testing + au! + au WinNew * nested edit somefile + au BufNew * let g:did_nested = 1 + augroup END + split + call assert_equal(1, g:did_nested) + close + bwipe! somefile + + " New ++nested argument works + augroup Testing + au! + au WinNew * ++nested edit somefile + au BufNew * let g:did_nested = 1 + augroup END + split + call assert_equal(1, g:did_nested) + close + bwipe! somefile + + augroup Testing + au! + augroup END + + call assert_fails('au WinNew * ++nested ++nested echo bad', 'E983:') + call assert_fails('au WinNew * nested nested echo bad', 'E983:') +endfunc + +func Test_autocmd_once() + " Without ++once WinNew triggers twice + let g:did_split = 0 + augroup Testing + au WinNew * let g:did_split += 1 + augroup END + split + split + call assert_equal(2, g:did_split) + call assert_true(exists('#WinNew')) + close + close + + " With ++once WinNew triggers once + let g:did_split = 0 + augroup Testing + au! + au WinNew * ++once let g:did_split += 1 + augroup END + split + split + call assert_equal(1, g:did_split) + call assert_false(exists('#WinNew')) + close + close + + 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_cd.vim b/src/nvim/testdir/test_cd.vim index 770ed55b8d..4436ebbf31 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -24,7 +24,10 @@ func Test_cd_no_arg() call assert_equal(path, getcwd()) else " Test that cd without argument echoes cwd on non-Unix systems. + let shellslash = &shellslash + set shellslash call assert_match(getcwd(), execute('cd')) + let &shellslash = shellslash endif 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_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index 60ef20e0b2..02840de743 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -35,7 +35,8 @@ func Test_client_server() endif " Takes a short while for the server to be active. - call WaitFor('serverlist() =~ "' . name . '"') + " When using valgrind it takes much longer. + call WaitFor('serverlist() =~ "' . name . '"', 5000) call assert_match(name, serverlist()) call remote_foreground(name) diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim index 2d793ed88f..7262789ab4 100644 --- a/src/nvim/testdir/test_command_count.vim +++ b/src/nvim/testdir/test_command_count.vim @@ -173,7 +173,6 @@ func Test_command_count_4() only! exe bufnr . 'buf' - bnext let bufnr = bufnr('%') let buffers = [] .,$-bufdo call add(buffers, bufnr('%')) 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_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index ad3eec3274..00f4563f3d 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -527,7 +527,7 @@ func Test_setting_cursor() new Xtest2 put =range(1,100) wq - + tabe Xtest2 $ diffsp Xtest1 @@ -672,6 +672,35 @@ func Test_diff_filler() %bwipe! endfunc +func Test_diff_hlID() + new + call setline(1, [1, 2, 3]) + diffthis + vnew + call setline(1, ['1x', 2, 'x', 3]) + diffthis + redraw + + call assert_equal(synIDattr(diff_hlID(-1, 1), "name"), "") + + call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) + call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") + call assert_equal(diff_hlID(1, 2), hlID("DiffText")) + call assert_equal(synIDattr(diff_hlID(1, 2), "name"), "DiffText") + call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") + call assert_equal(diff_hlID(3, 1), hlID("DiffAdd")) + call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "DiffAdd") + call assert_equal(synIDattr(diff_hlID(4, 1), "name"), "") + + wincmd w + call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) + call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") + call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") + call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "") + + %bwipe! +endfunc + func Test_diff_lastline() enew! only! @@ -734,6 +763,9 @@ func Test_diff_of_diff() call VerifyScreenDump(buf, 'Test_diff_of_diff_01', {}) + call term_sendkeys(buf, ":set rightleft\<cr>") + call VerifyScreenDump(buf, 'Test_diff_of_diff_02', {}) + " clean up call StopVimInTerminal(buf) call delete('Xtest_diff_diff') diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index f8946c6929..7f3994300f 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -402,8 +402,19 @@ func! Test_edit_13() call feedkeys("A {\<cr>more\<cr>}\<esc>", 'tnix') call assert_equal(["\tabc {", "\t\tmore", "\t}"], getline(1, '$')) set smartindent& autoindent& - bw! + bwipe! endif + + " Test autoindent removing indent of blank line. + new + call setline(1, ' foo bar baz') + set autoindent + exe "normal 0eea\<CR>\<CR>\<Esc>" + call assert_equal(" foo bar", getline(1)) + call assert_equal("", getline(2)) + call assert_equal(" baz", getline(3)) + set autoindent& + bwipe! endfunc func! Test_edit_CR() @@ -1374,9 +1385,26 @@ func Test_edit_complete_very_long_name() return endtry - " Try to get the Vim window position before setting 'columns'. + " Try to get the Vim window position before setting 'columns', so that we can + " move the window back to where it was. let winposx = getwinposx() let winposy = getwinposy() + + if winposx >= 0 && winposy >= 0 && !has('gui_running') + " We did get the window position, but xterm may report the wrong numbers. + " Move the window to the reported position and compute any offset. + exe 'winpos ' . winposx . ' ' . winposy + sleep 100m + let x = getwinposx() + if x >= 0 + let winposx += winposx - x + endif + let y = getwinposy() + if y >= 0 + let winposy += winposy - y + endif + endif + let save_columns = &columns " Need at least about 1100 columns to reproduce the problem. set columns=2000 @@ -1443,3 +1471,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_functions.vim b/src/nvim/testdir/test_functions.vim index 824baffbc9..46c2d0f4cd 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -881,14 +881,25 @@ func Test_Executable() call assert_equal(1, executable('notepad')) call assert_equal(1, executable('notepad.exe')) call assert_equal(0, executable('notepad.exe.exe')) - call assert_equal(1, executable('shell32.dll')) - call assert_equal(1, executable('win.ini')) + call assert_equal(0, executable('shell32.dll')) + call assert_equal(0, executable('win.ini')) elseif has('unix') call assert_equal(1, executable('cat')) call assert_equal(0, executable('nodogshere')) endif endfunc +func Test_executable_longname() + if !has('win32') + return + endif + + let fname = 'X' . repeat('ã‚', 200) . '.bat' + call writefile([], fname) + call assert_equal(1, executable(fname)) + call delete(fname) +endfunc + func Test_hostname() let hostname_vim = hostname() if has('unix') @@ -1049,3 +1060,19 @@ func Test_func_range_with_edit() call delete('Xfuncrange2') bwipe! endfunc + +sandbox function Fsandbox() + normal ix +endfunc + +func Test_func_sandbox() + sandbox let F = {-> 'hello'} + call assert_equal('hello', F()) + + sandbox let F = {-> execute("normal ix\<Esc>")} + call assert_fails('call F()', 'E48:') + unlet F + + call assert_fails('call Fsandbox()', 'E48:') + delfunc Fsandbox +endfunc 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_highlight.vim b/src/nvim/testdir/test_highlight.vim index 33df79581c..e751fb5d16 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -129,10 +129,6 @@ func Test_highlight_eol_with_cursorline() endfunc func Test_highlight_eol_with_cursorline_vertsplit() - if !has('vertsplit') - return - endif - let [hiCursorLine, hi_ul, hi_bg] = HiCursorLine() call NewWindow('topleft 5', 5) @@ -533,3 +529,45 @@ func Test_termguicolors() set t_Co=0 redraw endfunc + +func Test_cursorline_after_yank() + if !CanRunVimInTerminal() + return + endif + + call writefile([ + \ 'set cul rnu', + \ 'call setline(1, ["","1","2","3",""])', + \ ], 'Xtest_cursorline_yank') + let buf = RunVimInTerminal('-S Xtest_cursorline_yank', {'rows': 8}) + call term_wait(buf) + call term_sendkeys(buf, "Gy3k") + call term_wait(buf) + call term_sendkeys(buf, "jj") + + call VerifyScreenDump(buf, 'Test_cursorline_yank_01', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_cursorline_yank') +endfunc + +func Test_cursorline_with_visualmode() + if !CanRunVimInTerminal() + return + endif + + call writefile([ + \ 'set cul', + \ 'call setline(1, repeat(["abc"], 50))', + \ ], 'Xtest_cursorline_with_visualmode') + let buf = RunVimInTerminal('-S Xtest_cursorline_with_visualmode', {'rows': 12}) + call term_wait(buf) + call term_sendkeys(buf, "V\<C-f>kkkjk") + + call VerifyScreenDump(buf, 'Test_cursorline_with_visualmode_01', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_cursorline_with_visualmode') +endfunc diff --git a/src/nvim/testdir/test_jumplist.vim b/src/nvim/testdir/test_jumplist.vim new file mode 100644 index 0000000000..be1af5e705 --- /dev/null +++ b/src/nvim/testdir/test_jumplist.vim @@ -0,0 +1,66 @@ +" Tests for the jumplist functionality + +" Tests for the getjumplist() function +func Test_getjumplist() + if !has("jumplist") + return + endif + + %bwipe + clearjumps + call assert_equal([[], 0], getjumplist()) + call assert_equal([[], 0], getjumplist(1)) + call assert_equal([[], 0], getjumplist(1, 1)) + + call assert_equal([], getjumplist(100)) + call assert_equal([], getjumplist(1, 100)) + + let lines = [] + for i in range(1, 100) + call add(lines, "Line " . i) + endfor + call writefile(lines, "Xtest") + + " Jump around and create a jump list + edit Xtest + let bnr = bufnr('%') + normal 50% + normal G + normal gg + + let expected = [[ + \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 100, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 3] + call assert_equal(expected, getjumplist()) + " jumplist doesn't change in between calls + call assert_equal(expected, getjumplist()) + + " Traverse the jump list and verify the results + 5 + exe "normal \<C-O>" + call assert_equal(2, getjumplist(1)[1]) + exe "normal 2\<C-O>" + call assert_equal(0, getjumplist(1, 1)[1]) + exe "normal 3\<C-I>" + call assert_equal(3, getjumplist()[1]) + exe "normal \<C-O>" + normal 20% + let expected = [[ + \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 5, 'bufnr': bnr, 'col': 0, 'coladd': 0}, + \ {'lnum': 100, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 4] + call assert_equal(expected, getjumplist()) + " jumplist doesn't change in between calls + call assert_equal(expected, getjumplist()) + + let l = getjumplist() + call test_garbagecollect_now() + call assert_equal(4, l[1]) + clearjumps + call test_garbagecollect_now() + call assert_equal(4, l[1]) + + call delete("Xtest") +endfunc diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index ada25da4a8..bc7817cef8 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -291,3 +291,9 @@ func Test_named_function_closure() call garbagecollect() call assert_equal(14, s:Abar()) endfunc + +func Test_lambda_with_index() + let List = {x -> [x]} + let Extract = {-> function(List, ['foobar'])()[0]} + call assert_equal('foobar', Extract()) +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_match.vim b/src/nvim/testdir/test_match.vim index e608a2e58b..e926b946a9 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -202,6 +202,28 @@ func Test_matchaddpos() set hlsearch& endfunc +func Test_matchaddpos_otherwin() + syntax on + new + call setline(1, ['12345', 'NP']) + let winid = win_getid() + + wincmd w + call matchadd('Search', '4', 10, -1, {'window': winid}) + call matchaddpos('Error', [[1,2], [2,2]], 10, -1, {'window': winid}) + redraw! + call assert_notequal(screenattr(1,2), 0) + call assert_notequal(screenattr(1,4), 0) + call assert_notequal(screenattr(2,2), 0) + call assert_equal(screenattr(1,2), screenattr(2,2)) + call assert_notequal(screenattr(1,2), screenattr(1,4)) + + wincmd w + bwipe! + call clearmatches() + syntax off +endfunc + func Test_matchaddpos_using_negative_priority() set hlsearch diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index c2e7bb7bf9..c790bd32e3 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -17,9 +17,9 @@ func Test_mksession() \ ' four leadinG spaces', \ 'two consecutive tabs', \ 'two tabs in one line', - \ 'one ä multibyteCharacter', - \ 'aä Ä two multiByte characters', - \ 'Aäöü three mulTibyte characters', + \ 'one ä multibyteCharacter', + \ 'aä Ä two multiByte characters', + \ 'Aäöü three mulTibyte characters', \ 'short line', \ ]) let tmpfile = 'Xtemp' @@ -240,13 +240,14 @@ endfunc func Test_mksession_quote_in_filename() let v:errmsg = '' + let filename = has('win32') ? 'x''y' : 'x''y"z' %bwipe! split another - split x'y\"z + execute 'split' escape(filename, '"') mksession! Xtest_mks_quoted.out %bwipe! source Xtest_mks_quoted.out - call assert_true(bufexists("x'y\"z")) + call assert_true(bufexists(filename)) %bwipe! call delete('Xtest_mks_quoted.out') diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim new file mode 100644 index 0000000000..75fe1d993c --- /dev/null +++ b/src/nvim/testdir/test_modeline.vim @@ -0,0 +1,84 @@ +func Test_modeline_invalid() + let modeline = &modeline + set modeline + call assert_fails('set Xmodeline', 'E518:') + + let &modeline = modeline + bwipe! + call delete('Xmodeline') + endfunc + +func Test_modeline_filetype() + call writefile(['vim: set ft=c :', 'nothing'], 'Xmodeline_filetype') + let modeline = &modeline + set modeline + filetype plugin on + split Xmodeline_filetype + call assert_equal("c", &filetype) + call assert_equal(1, b:did_ftplugin) + call assert_equal("ccomplete#Complete", &ofu) + + bwipe! + call delete('Xmodeline_filetype') + let &modeline = modeline + filetype plugin off +endfunc + +func Test_modeline_syntax() + call writefile(['vim: set syn=c :', 'nothing'], 'Xmodeline_syntax') + let modeline = &modeline + set modeline + syntax enable + split Xmodeline_syntax + call assert_equal("c", &syntax) + call assert_equal("c", b:current_syntax) + + bwipe! + call delete('Xmodeline_syntax') + let &modeline = modeline + syntax off +endfunc + +func Test_modeline_keymap() + call writefile(['vim: set keymap=greek :', 'nothing'], 'Xmodeline_keymap') + let modeline = &modeline + set modeline + split Xmodeline_keymap + call assert_equal("greek", &keymap) + call assert_match('greek\|grk', b:keymap_name) + + bwipe! + call delete('Xmodeline_keymap') + let &modeline = modeline + set keymap= iminsert=0 imsearch=-1 +endfunc + +func s:modeline_fails(what, text) + let fname = "Xmodeline_fails_" . a:what + call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname) + let modeline = &modeline + set modeline + filetype plugin on + syntax enable + call assert_fails('split ' . fname, 'E474:') + call assert_equal("", &filetype) + call assert_equal("", &syntax) + + bwipe! + call delete(fname) + let &modeline = modeline + filetype plugin off + syntax off +endfunc + +func Test_modeline_filetype_fails() + call s:modeline_fails('filetype', 'ft=evil$CMD') +endfunc + +func Test_modeline_syntax_fails() + call s:modeline_fails('syntax', 'syn=evil$CMD') +endfunc + +func Test_modeline_keymap_fails() + call s:modeline_fails('keymap', 'keymap=evil$CMD') +endfunc 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_popup.vim b/src/nvim/testdir/test_popup.vim index 6c43cbc1dc..96f4bfc71b 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -5,7 +5,7 @@ source shared.vim let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] let g:setting = '' -func! ListMonths() +func ListMonths() if g:setting != '' exe ":set" g:setting endif @@ -18,7 +18,7 @@ func! ListMonths() return '' endfunc -func! Test_popup_complete2() +func Test_popup_complete2() " Although the popupmenu is not visible, this does not mean completion mode " has ended. After pressing <f5> to complete the currently typed char, Vim " still stays in the first state of the completion (:h ins-completion-menu), @@ -33,9 +33,9 @@ func! Test_popup_complete2() call assert_equal(["Dece", "", "December2015"], getline(1,3)) %d bw! -endfu +endfunc -func! Test_popup_complete() +func Test_popup_complete() new inoremap <f5> <c-r>=ListMonths()<cr> @@ -214,10 +214,10 @@ func! Test_popup_complete() call feedkeys("aM\<f5>\<enter>\<esc>", 'tx') call assert_equal(["March", "M", "March"], getline(1,4)) %d -endfu +endfunc -func! Test_popup_completion_insertmode() +func Test_popup_completion_insertmode() new inoremap <F5> <C-R>=ListMonths()<CR> @@ -246,20 +246,16 @@ func! Test_popup_completion_insertmode() iunmap <F5> endfunc -" TODO: Fix what breaks after this line. -" - Do not use "q!", it may exit Vim if there is an error -finish - func Test_noinsert_complete() - function! s:complTest1() abort + func! s:complTest1() abort call complete(1, ['source', 'soundfold']) return '' - endfunction + endfunc - function! s:complTest2() abort + func! s:complTest2() abort call complete(1, ['source', 'soundfold']) return '' - endfunction + endfunc new set completeopt+=noinsert @@ -279,10 +275,42 @@ func Test_noinsert_complete() iunmap <F5> endfunc +func Test_complete_no_filter() + func! s:complTest1() abort + call complete(1, [{'word': 'foobar'}]) + return '' + endfunc + func! s:complTest2() abort + call complete(1, [{'word': 'foobar', 'equal': 1}]) + return '' + endfunc + + let completeopt = &completeopt + + " without equal=1 + new + set completeopt=menuone,noinsert,menu + inoremap <F5> <C-R>=s:complTest1()<CR> + call feedkeys("i\<F5>z\<CR>\<CR>\<ESC>.", 'tx') + call assert_equal('z', getline(1)) + bwipe! + + " with equal=1 + new + set completeopt=menuone,noinsert,menu + inoremap <F5> <C-R>=s:complTest2()<CR> + call feedkeys("i\<F5>z\<CR>\<CR>\<ESC>.", 'tx') + call assert_equal('foobar', getline(1)) + bwipe! + + let &completeopt = completeopt + iunmap <F5> +endfunc + func Test_compl_vim_cmds_after_register_expr() - function! s:test_func() + func! s:test_func() return 'autocmd ' - endfunction + endfunc augroup AAAAA_Group au! augroup END @@ -329,7 +357,7 @@ func DummyCompleteTwo(findstart, base) else return ['twodef', 'twoDEF'] endif -endfunction +endfunc " Test that nothing happens if the 'completefunc' opens " a new window (no completion, no crash) @@ -406,7 +434,7 @@ func Test_omnifunc_with_check() q! endfunc -function UndoComplete() +func UndoComplete() call complete(1, ['January', 'February', 'March', \ 'April', 'May', 'June', 'July', 'August', 'September', \ 'October', 'November', 'December']) @@ -443,7 +471,7 @@ func Test_complete_no_undo() q! endfunc -function! DummyCompleteFive(findstart, base) +func DummyCompleteFive(findstart, base) if a:findstart return 0 else @@ -547,7 +575,7 @@ func Test_completion_comment_formatting() bwipe! endfunc -function! DummyCompleteSix() +func DummyCompleteSix() call complete(1, ['Hello', 'World']) return '' endfunction @@ -623,7 +651,7 @@ func Test_popup_and_preview_autocommand() bw! endfunc -fun MessCompleteMonths() +func MessCompleteMonths() for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep") call complete_add(m) if complete_check() @@ -631,14 +659,14 @@ fun MessCompleteMonths() endif endfor return [] -endfun +endfunc -fun MessCompleteMore() +func MessCompleteMore() call complete(1, split("Oct Nov Dec")) return [] -endfun +endfunc -fun MessComplete(findstart, base) +func MessComplete(findstart, base) if a:findstart let line = getline('.') let start = col('.') - 1 @@ -651,7 +679,7 @@ fun MessComplete(findstart, base) call MessCompleteMore() return [] endif -endf +endfunc func Test_complete_func_mess() " Calling complete() after complete_add() in 'completefunc' is wrong, but it @@ -712,4 +740,105 @@ func Test_popup_and_window_resize() bwipe! endfunc +func Test_popup_complete_info_01() + new + inoremap <buffer><F5> <C-R>=complete_info().mode<CR> + func s:complTestEval() abort + call complete(1, ['aa', 'ab']) + return '' + endfunc + inoremap <buffer><F6> <C-R>=s:complTestEval()<CR> + call writefile([ + \ 'dummy dummy.txt 1', + \], 'Xdummy.txt') + setlocal tags=Xdummy.txt + setlocal dictionary=Xdummy.txt + setlocal thesaurus=Xdummy.txt + setlocal omnifunc=syntaxcomplete#Complete + setlocal completefunc=syntaxcomplete#Complete + setlocal spell + for [keys, mode_name] in [ + \ ["", ''], + \ ["\<C-X>", 'ctrl_x'], + \ ["\<C-X>\<C-N>", 'keyword'], + \ ["\<C-X>\<C-P>", 'keyword'], + \ ["\<C-X>\<C-L>", 'whole_line'], + \ ["\<C-X>\<C-F>", 'files'], + \ ["\<C-X>\<C-]>", 'tags'], + \ ["\<C-X>\<C-D>", 'path_defines'], + \ ["\<C-X>\<C-I>", 'path_patterns'], + \ ["\<C-X>\<C-K>", 'dictionary'], + \ ["\<C-X>\<C-T>", 'thesaurus'], + \ ["\<C-X>\<C-V>", 'cmdline'], + \ ["\<C-X>\<C-U>", 'function'], + \ ["\<C-X>\<C-O>", 'omni'], + \ ["\<C-X>s", 'spell'], + \ ["\<F6>", 'eval'], + \] + call feedkeys("i" . keys . "\<F5>\<Esc>", 'tx') + call assert_equal(mode_name, getline('.')) + %d + endfor + call delete('Xdummy.txt') + bwipe! +endfunc + +func UserDefinedComplete(findstart, base) + if a:findstart + return 0 + else + return [ + \ { 'word': 'Jan', 'menu': 'January' }, + \ { 'word': 'Feb', 'menu': 'February' }, + \ { 'word': 'Mar', 'menu': 'March' }, + \ { 'word': 'Apr', 'menu': 'April' }, + \ { 'word': 'May', 'menu': 'May' }, + \ ] + endif +endfunc + +func GetCompleteInfo() + if empty(g:compl_what) + let g:compl_info = complete_info() + else + let g:compl_info = complete_info(g:compl_what) + endif + return '' +endfunc + +func Test_popup_complete_info_02() + new + inoremap <buffer><F5> <C-R>=GetCompleteInfo()<CR> + setlocal completefunc=UserDefinedComplete + + let d = { + \ 'mode': 'function', + \ 'pum_visible': 1, + \ 'items': [ + \ {'word': 'Jan', 'menu': 'January', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'Feb', 'menu': 'February', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'Mar', 'menu': 'March', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'Apr', 'menu': 'April', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'May', 'menu': 'May', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''} + \ ], + \ 'selected': 0, + \ } + + let g:compl_what = [] + call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx') + call assert_equal(d, g:compl_info) + + let g:compl_what = ['mode', 'pum_visible', 'selected'] + call remove(d, 'items') + call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx') + call assert_equal(d, g:compl_info) + + let g:compl_what = ['mode'] + call remove(d, 'selected') + call remove(d, 'pum_visible') + call feedkeys("i\<C-X>\<C-U>\<F5>", 'tx') + call assert_equal(d, g:compl_info) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index 0b8961c52b..43a5d18cb3 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -1,3 +1,4 @@ +" Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc. func Test_put_block() if !has('multi_byte') @@ -47,3 +48,59 @@ func Test_put_expr() call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1,'$')) bw! endfunc + +func Test_put_lines() + new + let a = [ getreg('a'), getregtype('a') ] + call setline(1, ['Line 1', 'Line2', 'Line 3', '']) + exe 'norm! gg"add"AddG""p' + call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1,'$')) + " clean up + bw! + call setreg('a', a[0], a[1]) +endfunc + +func Test_put_fails_when_nomodifiable() + new + setlocal nomodifiable + + normal! yy + call assert_fails(':put', 'E21') + call assert_fails(':put!', 'E21') + call assert_fails(':normal! p', 'E21') + call assert_fails(':normal! gp', 'E21') + call assert_fails(':normal! P', 'E21') + call assert_fails(':normal! gP', 'E21') + + if has('mouse') + set mouse=n + call assert_fails('execute "normal! \<MiddleMouse>"', 'E21') + set mouse& + endif + + bwipeout! +endfunc + +" A bug was discovered where the Normal mode put commands (e.g., "p") would +" output duplicate error messages when invoked in a non-modifiable buffer. +func Test_put_p_errmsg_nodup() + new + setlocal nomodifiable + + normal! yy + + func Capture_p_error() + redir => s:p_err + normal! p + redir END + endfunc + + silent! call Capture_p_error() + + " Error message output within a function should be three lines (the function + " name, the line number, and the error message). + call assert_equal(3, count(s:p_err, "\n")) + + delfunction Capture_p_error + bwipeout! +endfunc diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim index 5ba9fd68cf..0c5809f7a4 100644 --- a/src/nvim/testdir/test_python2.vim +++ b/src/nvim/testdir/test_python2.vim @@ -52,3 +52,92 @@ func Test_vim_function() py del f delfunc s:foo endfunc + +func _SetUpHiddenBuffer() + py import vim + new + edit hidden + setlocal bufhidden=hide + + enew + let lnum = 0 + while lnum < 10 + call append( 1, string( lnum ) ) + let lnum = lnum + 1 + endwhile + normal G + + call assert_equal( line( '.' ), 11 ) +endfunc + +func _CleanUpHiddenBuffer() + bwipe! hidden + bwipe! +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear() + call _SetUpHiddenBuffer() + py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List() + call _SetUpHiddenBuffer() + py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ] + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str() + call _SetUpHiddenBuffer() + py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test' + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine() + call _SetUpHiddenBuffer() + py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func _SetUpVisibleBuffer() + py import vim + new + let lnum = 0 + while lnum < 10 + call append( 1, string( lnum ) ) + let lnum = lnum + 1 + endwhile + normal G + call assert_equal( line( '.' ), 11 ) +endfunc + +func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear() + call _SetUpVisibleBuffer() + + py vim.current.buffer[:] = None + call assert_equal( line( '.' ), 1 ) + + bwipe! +endfunc + +func Test_Write_To_Current_Buffer_Fixes_Cursor_List() + call _SetUpVisibleBuffer() + + py vim.current.buffer[:] = [ 'test' ] + call assert_equal( line( '.' ), 1 ) + + bwipe! +endfunction + +func Test_Write_To_Current_Buffer_Fixes_Cursor_Str() + call _SetUpVisibleBuffer() + + py vim.current.buffer[-1] = None + call assert_equal( line( '.' ), 10 ) + + bwipe! +endfunction diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 2e3fc93674..d090dcc99c 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -52,3 +52,92 @@ func Test_vim_function() py3 del f delfunc s:foo endfunc + +func _SetUpHiddenBuffer() + py3 import vim + new + edit hidden + setlocal bufhidden=hide + + enew + let lnum = 0 + while lnum < 10 + call append( 1, string( lnum ) ) + let lnum = lnum + 1 + endwhile + normal G + + call assert_equal( line( '.' ), 11 ) +endfunc + +func _CleanUpHiddenBuffer() + bwipe! hidden + bwipe! +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear() + call _SetUpHiddenBuffer() + py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List() + call _SetUpHiddenBuffer() + py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ] + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str() + call _SetUpHiddenBuffer() + py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test' + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine() + call _SetUpHiddenBuffer() + py3 vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None + call assert_equal( line( '.' ), 11 ) + call _CleanUpHiddenBuffer() +endfunc + +func _SetUpVisibleBuffer() + py3 import vim + new + let lnum = 0 + while lnum < 10 + call append( 1, string( lnum ) ) + let lnum = lnum + 1 + endwhile + normal G + call assert_equal( line( '.' ), 11 ) +endfunc + +func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear() + call _SetUpVisibleBuffer() + + py3 vim.current.buffer[:] = None + call assert_equal( line( '.' ), 1 ) + + bwipe! +endfunc + +func Test_Write_To_Current_Buffer_Fixes_Cursor_List() + call _SetUpVisibleBuffer() + + py3 vim.current.buffer[:] = [ 'test' ] + call assert_equal( line( '.' ), 1 ) + + bwipe! +endfunction + +func Test_Write_To_Current_Buffer_Fixes_Cursor_Str() + call _SetUpVisibleBuffer() + + py3 vim.current.buffer[-1] = None + call assert_equal( line( '.' ), 10 ) + + bwipe! +endfunction diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index cb3e7ca8f6..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 @@ -430,6 +443,19 @@ func Xtest_browse(cchar) call delete('Xqftestfile1') call delete('Xqftestfile2') + + " Should be able to use next/prev with invalid entries + Xexpr "" + call assert_equal(0, g:Xgetlist({'idx' : 0}).idx) + call assert_equal(0, g:Xgetlist({'size' : 0}).size) + Xaddexpr ['foo', 'bar', 'baz', 'quux', 'shmoo'] + call assert_equal(5, g:Xgetlist({'size' : 0}).size) + Xlast + call assert_equal(5, g:Xgetlist({'idx' : 0}).idx) + Xfirst + call assert_equal(1, g:Xgetlist({'idx' : 0}).idx) + 2Xnext + call assert_equal(3, g:Xgetlist({'idx' : 0}).idx) endfunc func Test_browse() @@ -469,6 +495,19 @@ func s:test_xhelpgrep(cchar) " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + if a:cchar == 'l' + " When a help window is present, running :lhelpgrep should reuse the + " help window and not the current window + new | only + call g:Xsetlist([], 'f') + help index.txt + wincmd w + lhelpgrep quickfix + call assert_equal(1, winnr()) + call assert_notequal([], getloclist(1)) + call assert_equal([], getloclist(2)) + endif + new | only " Search for non existing help string @@ -1067,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'] @@ -1353,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') @@ -1666,6 +1725,10 @@ func HistoryTest(cchar) call assert_equal(' error list 1 of 3; 1 ' . common, res[0]) call assert_equal(' error list 2 of 3; 2 ' . common, res[1]) call assert_equal('> error list 3 of 3; 3 ' . common, res[2]) + + call g:Xsetlist([], 'f') + let l = split(execute(a:cchar . 'hist'), "\n") + call assert_equal('No entries', l[0]) endfunc func Test_history() @@ -1749,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})) @@ -1786,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 @@ -1844,6 +1907,11 @@ func Xproperty_tests(cchar) let l = g:Xgetlist({'items':1}) call assert_equal(0, len(l.items)) + call g:Xsetlist([], 'r', {'title' : 'TestTitle'}) + call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) + call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) + call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title) + " The following used to crash Vim with address sanitizer call g:Xsetlist([], 'f') call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]}) @@ -1886,10 +1954,10 @@ func Xproperty_tests(cchar) call g:Xsetlist([], 'r', l2) let newl1=g:Xgetlist({'nr':1,'all':1}) let newl2=g:Xgetlist({'nr':2,'all':1}) - call assert_equal(':Fruits', newl1.title) + call assert_equal('Fruits', newl1.title) call assert_equal(['Fruits'], newl1.context) call assert_equal('Line20', newl1.items[0].text) - call assert_equal(':Colors', newl2.title) + call assert_equal('Colors', newl2.title) call assert_equal(['Colors'], newl2.context) call assert_equal('Line10', newl2.items[0].text) call g:Xsetlist([], 'f') @@ -1913,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") @@ -1920,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() @@ -2184,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" @@ -2405,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() @@ -2584,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 '' @@ -2592,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) @@ -2606,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() @@ -2614,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() @@ -2638,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 @@ -2664,3 +2970,198 @@ func Test_qfwin_pos() call assert_equal(3, winnr()) 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() + enew | only + call writefile(["Test"], 'Xfile1') + call setqflist([], 'f') + copen | only + let qfbuf = bufnr('') + edit Xfile1 + call assert_notequal(qfbuf, bufnr('')) + 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_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim index 2192b5e8fc..7873502943 100644 --- a/src/nvim/testdir/test_regex_char_classes.vim +++ b/src/nvim/testdir/test_regex_char_classes.vim @@ -1,5 +1,11 @@ " Tests for regexp with backslash and other special characters inside [] " Also test backslash for hex/octal numbered character. +" +if !has('multi_byte') + finish +endif + +scriptencoding utf-8 function RunSTest(value, calls, expected) new @@ -56,3 +62,237 @@ function Test_s_search() call RunSTest(" xyz", "s/~/bcd/", " bcd") call RunSTest(" bcdbcdbcd", "s/~\\+/BB/", " BB") endfunction + +" Test character classes in regexp using regexpengine 0, 1, 2. +func Test_regex_char_classes() + new + let save_enc = &encoding + set encoding=utf-8 + + let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé" + + " Format is [cmd_to_run, expected_output] + let tests = [ + \ [':s/\%#=0\d//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\d//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\d//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[0-9]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[0-9]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[0-9]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\D//g', + \ "0123456789"], + \ [':s/\%#=1\D//g', + \ "0123456789"], + \ [':s/\%#=2\D//g', + \ "0123456789"], + \ [':s/\%#=0[^0-9]//g', + \ "0123456789"], + \ [':s/\%#=1[^0-9]//g', + \ "0123456789"], + \ [':s/\%#=2[^0-9]//g', + \ "0123456789"], + \ [':s/\%#=0\o//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\o//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\o//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[0-7]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[0-7]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[0-7]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\O//g', + \ "01234567"], + \ [':s/\%#=1\O//g', + \ "01234567"], + \ [':s/\%#=2\O//g', + \ "01234567"], + \ [':s/\%#=0[^0-7]//g', + \ "01234567"], + \ [':s/\%#=1[^0-7]//g', + \ "01234567"], + \ [':s/\%#=2[^0-7]//g', + \ "01234567"], + \ [':s/\%#=0\x//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\x//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\x//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[0-9A-Fa-f]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[0-9A-Fa-f]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[0-9A-Fa-f]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\X//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=1\X//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=2\X//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=0[^0-9A-Fa-f]//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=1[^0-9A-Fa-f]//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=2[^0-9A-Fa-f]//g', + \ "0123456789ABCDEFabcdef"], + \ [':s/\%#=0\w//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\w//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\w//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[0-9A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[0-9A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[0-9A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\W//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1\W//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2\W//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0[^0-9A-Za-z_]//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1[^0-9A-Za-z_]//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2[^0-9A-Za-z_]//g', + \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0\h//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\h//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\h//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[A-Za-z_]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\H//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1\H//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2\H//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0[^A-Za-z_]//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=1[^A-Za-z_]//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=2[^A-Za-z_]//g', + \ "ABCDEFGHIXYZ_abcdefghiwxyz"], + \ [':s/\%#=0\a//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\a//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\a//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[A-Za-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[A-Za-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[A-Za-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\A//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=1\A//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=2\A//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=0[^A-Za-z]//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=1[^A-Za-z]//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=2[^A-Za-z]//g', + \ "ABCDEFGHIXYZabcdefghiwxyz"], + \ [':s/\%#=0\l//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\l//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\l//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[a-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[a-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[a-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\L//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=1\L//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=2\L//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=0[^a-z]//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=1[^a-z]//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=2[^a-z]//g', + \ "abcdefghiwxyz"], + \ [':s/\%#=0\u//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\u//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\u//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[A-Z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[A-Z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[A-Z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0\U//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=1\U//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=2\U//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=0[^A-Z]//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=1[^A-Z]//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=2[^A-Z]//g', + \ "ABCDEFGHIXYZ"], + \ [':s/\%#=0\%' . line('.') . 'l^\t...//g', + \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1\%' . line('.') . 'l^\t...//g', + \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2\%' . line('.') . 'l^\t...//g', + \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[0-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=1[0-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=2[0-z]//g', + \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"], + \ [':s/\%#=0[^0-z]//g', + \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"], + \ [':s/\%#=1[^0-z]//g', + \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"], + \ [':s/\%#=2[^0-z]//g', + \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"] + \] + + for [cmd, expected] in tests + call append(0, input) + call cursor(1, 1) + exe cmd + call assert_equal(expected, getline(1), cmd) + endfor + + let &encoding = save_enc + enew! + close +endfunc diff --git a/src/nvim/testdir/test_scrollbind.vim b/src/nvim/testdir/test_scrollbind.vim index baa24f1979..6c5488be05 100644 --- a/src/nvim/testdir/test_scrollbind.vim +++ b/src/nvim/testdir/test_scrollbind.vim @@ -30,3 +30,243 @@ func Test_scrollbind() setl noscrollbind call assert_equal(0, topLineLeft - topLineRight) endfunc + +" Test for 'scrollbind' +func Test_scrollbind_opt() + new | only + set noscrollbind + set scrollopt=ver,jump scrolloff=2 nowrap noequalalways splitbelow + + " Insert the text used for the test + append + + +start of window 1 +. line 01 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 01 +. line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02 +. line 03 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 03 +. line 04 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 04 +. line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05 +. line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06 +. line 07 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 07 +. line 08 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 08 +. line 09 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 09 +. line 10 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 10 +. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11 +. line 12 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 12 +. line 13 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 13 +. line 14 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 14 +. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15 +end of window 1 + + +start of window 2 +. line 01 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 01 +. line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02 +. line 03 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 03 +. line 04 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 04 +. line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05 +. line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06 +. line 07 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 07 +. line 08 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 08 +. line 09 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 09 +. line 10 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 10 +. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11 +. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12 +. line 13 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 13 +. line 14 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 14 +. line 15 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 15 +. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16 +end of window 2 + +. + + " Test using two windows open to one buffer, one extra empty window + split + new + wincmd t + resize 8 + call search('^start of window 1$') + normal zt + set scrollbind + wincmd j + resize 7 + call search('^start of window 2$') + normal zt + set scrollbind + + " -- start of tests -- + " Test scrolling down + normal L5jHyy + wincmd b | normal pr0 + wincmd t | normal Hyy + wincmd b | normal pr1 + wincmd t | normal L6jHyy + wincmd b | normal pr2 + wincmd k | normal Hyy + wincmd b | normal pr3 + + " Test scrolling up + wincmd t | normal H4k + wincmd j | normal H + wincmd t | normal Hyy + wincmd b | normal pr4 + wincmd k | normal Hyy + wincmd b | normal pr5 + wincmd k | normal 3k + wincmd t | normal H + wincmd j | normal Hyy + wincmd b | normal pr6 + wincmd t | normal Hyy + wincmd b | normal pr7 + + " Test horizontal scrolling + set scrollopt+=hor + normal gg"zyyG"zpG + wincmd t | normal 015zly$ + wincmd b | normal p"zpG + wincmd k | normal y$ + wincmd b | normal p"zpG + wincmd k | normal 10jH7zhg0y$ + wincmd b | normal p"zpG + wincmd t | normal Hg0y$ + wincmd b | normal p"zpG + set scrollopt-=hor + + wincmd b + call assert_equal([ + \ '', + \ '0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05', + \ '1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05', + \ '2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11', + \ '3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11', + \ '4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06', + \ '5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06', + \ '6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02', + \ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02', + \ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02', + \ 'UTSRQPONMLKJIHGREDCBA9876543210 02', + \ '. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11', + \ '. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11', + \ ''], getline(1, '$')) + enew! + + " ****** tests using two different buffers ***** + wincmd t | wincmd j | close + wincmd t | set noscrollbind + /start of window 2$/,/^end of window 2$/y + new + wincmd t | wincmd j | normal 4"zpGp + wincmd t + call search('^start of window 1$') + normal zt + set scrollbind + wincmd j + call search('^start of window 2$') + normal zt + set scrollbind + + " -- start of tests -- + " Test scrolling down + normal L5jHyy + wincmd b | normal pr0 + wincmd t | normal Hyy + wincmd b | normal pr1 + wincmd t | normal L6jHyy + wincmd b | normal pr2 + wincmd k | normal Hyy + wincmd b | normal pr3 + + " Test scrolling up + wincmd t | normal H4k + wincmd j | normal H + wincmd t | normal Hyy + wincmd b | normal pr4 + wincmd k | normal Hyy + wincmd b | normal pr5 + wincmd k | normal 3k + wincmd t | normal H + wincmd j | normal Hyy + wincmd b | normal pr6 + wincmd t | normal Hyy + wincmd b | normal pr7 + + " Test horizontal scrolling + set scrollopt+=hor + normal gg"zyyG"zpG + wincmd t | normal 015zly$ + wincmd b | normal p"zpG + wincmd k | normal y$ + wincmd b | normal p"zpG + wincmd k | normal 10jH7zhg0y$ + wincmd b | normal p"zpG + wincmd t | normal Hg0y$ + wincmd b | normal p"zpG + set scrollopt-=hor + + wincmd b + call assert_equal([ + \ '', + \ '0 line 05 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 05', + \ '1 line 05 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 05', + \ '2 line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11', + \ '3 line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11', + \ '4 line 06 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 06', + \ '5 line 06 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 06', + \ '6 line 02 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 02', + \ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02', + \ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02', + \ 'UTSRQPONMLKJIHGREDCBA9876543210 02', + \ '. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11', + \ '. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11', + \ ''], getline(1, '$')) + enew! + + " Test 'syncbind' + wincmd t | set noscrollbind | normal ggL + wincmd j | set noscrollbind | normal ggL + set scrollbind + wincmd t | set scrollbind | normal G + wincmd j | normal G + syncbind + normal Hk + wincmd t | normal H + wincmd j | normal Hyy + wincmd b | normal p + wincmd t | normal yy + wincmd b | normal p + wincmd t | set noscrollbind | normal ggL + wincmd j | set noscrollbind + normal ggL + set scrollbind + wincmd t | set scrollbind + wincmd t | normal G + wincmd j | normal G + wincmd t | syncbind | normal Hk + wincmd j | normal H + wincmd t | normal Hyy + wincmd b | normal p + wincmd t | wincmd j | normal yy + wincmd b | normal p + wincmd t | normal H3k + wincmd j | normal H + wincmd t | normal Hyy + wincmd b | normal p + wincmd t | wincmd j | normal yy + wincmd b | normal p + + wincmd b + call assert_equal([ + \ '', + \ '. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16', + \ 'start of window 2', + \ 'start of window 2', + \ '. line 16 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 16', + \ '. line 15 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 15', + \ '. line 12 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 12', + \ ], getline(1, '$')) + enew! + + new | only! + set scrollbind& scrollopt& scrolloff& wrap& equalalways& splitbelow& +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 d02454fbf0..8b306192f0 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -107,6 +107,32 @@ function! Test_substitute_variants() endfor endfunction +" Test the l, p, # flags. +func Test_substitute_flags_lp() + new + call setline(1, "abc\tdef\<C-h>ghi") + + let a = execute('s/a/a/p') + call assert_equal("\nabc def^Hghi", a) + + let a = execute('s/a/a/l') + call assert_equal("\nabc^Idef^Hghi$", a) + + let a = execute('s/a/a/#') + call assert_equal("\n 1 abc def^Hghi", a) + + let a = execute('s/a/a/p#') + call assert_equal("\n 1 abc def^Hghi", a) + + let a = execute('s/a/a/l#') + call assert_equal("\n 1 abc^Idef^Hghi$", a) + + let a = execute('s/a/a/') + call assert_equal("", a) + + bwipe! +endfunc + func Test_substitute_repeat() " This caused an invalid memory access. split Xfile @@ -585,3 +611,93 @@ func Test_sub_replace_10() call assert_equal('aa2a3a', substitute('123', '1\|\ze', 'a', 'g')) call assert_equal('1aaa', substitute('123', '1\zs\|[23]', 'a', 'g')) endfunc + +func Test_nocatch_sub_failure_handling() + " normal error results in all replacements + func! Foo() + foobar + endfunc + new + call setline(1, ['1 aaa', '2 aaa', '3 aaa']) + %s/aaa/\=Foo()/g + call assert_equal(['1 0', '2 0', '3 0'], getline(1, 3)) + + " Trow without try-catch causes abort after the first line. + " We cannot test this, since it would stop executing the test script. + + " try/catch does not result in any changes + func! Foo() + throw 'error' + endfunc + call setline(1, ['1 aaa', '2 aaa', '3 aaa']) + let error_caught = 0 + try + %s/aaa/\=Foo()/g + catch + let error_caught = 1 + endtry + call assert_equal(1, error_caught) + call assert_equal(['1 aaa', '2 aaa', '3 aaa'], getline(1, 3)) + + " Same, but using "n" flag so that "sandbox" gets set + call setline(1, ['1 aaa', '2 aaa', '3 aaa']) + let error_caught = 0 + try + %s/aaa/\=Foo()/gn + catch + let error_caught = 1 + endtry + call assert_equal(1, error_caught) + call assert_equal(['1 aaa', '2 aaa', '3 aaa'], getline(1, 3)) + + 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 f9bd8b5246..21ea00cab7 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -258,4 +258,209 @@ 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:()', + \ ], 'Xtags') + call writefile([ + \ 'main Xtest.c /^int main()$/;" f typeref:typename:int signature:()', + \ 'test Xtest.c /^void test()$/;" f typeref:typename:void signature:()', + \ ], 'Ytags') + cal writefile([ + \ 'int main()', + \ 'void test()', + \ ], 'Xtest.c') + cal writefile([ + \ 'void test();', + \ ], 'Xtest.h') + set tags=Xtags,Ytags + + new Xtest.c + let tl = taglist('test', 'Xtest.c') + call assert_equal(tl[0].filename, 'Xtest.c') + call assert_equal(tl[1].filename, 'Xtest.h') + + tag test + call assert_equal(bufname('%'), 'Xtest.c') + 1tag test + call assert_equal(bufname('%'), 'Xtest.c') + 2tag test + call assert_equal(bufname('%'), 'Xtest.h') + + set tags& + call delete('Xtags') + call delete('Ytags') + bwipe Xtest.h + bwipe Xtest.c + call delete('Xtest.h') + call delete('Xtest.c') +endfunc + +func Test_tagnr_recall() + call writefile([ + \ 'test Xtest.h /^void test();$/;" p', + \ 'main Xtest.c /^int main()$/;" f', + \ 'test Xtest.c /^void test()$/;" f', + \ ], 'Xtags') + cal writefile([ + \ 'int main()', + \ 'void test()', + \ ], 'Xtest.c') + cal writefile([ + \ 'void test();', + \ ], 'Xtest.h') + set tags=Xtags + + new Xtest.c + let tl = taglist('test', 'Xtest.c') + call assert_equal(tl[0].filename, 'Xtest.c') + call assert_equal(tl[1].filename, 'Xtest.h') + + 2tag test + call assert_equal(bufname('%'), 'Xtest.h') + pop + call assert_equal(bufname('%'), 'Xtest.c') + tag + call assert_equal(bufname('%'), 'Xtest.h') + + set tag& + call delete('Xtags') + bwipe Xtest.h + bwipe Xtest.c + call delete('Xtest.h') + call delete('Xtest.c') +endfunc + +func Test_tag_line_toolong() + call writefile([ + \ '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML' + \ ], 'Xtags') + set tags=Xtags + let old_vbs = &verbose + set verbose=5 + " ":tjump" should give "tag not found" not "Format error in tags file" + call assert_fails('tj /foo', 'E426') + try + tj /foo + catch /^Vim\%((\a\+)\)\=:E431/ + call assert_report(v:exception) + catch /.*/ + endtry + call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) + call writefile([ + \ '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567 django/contrib/admin/templates/admin/edit_inline/stacked.html 16;" j line:16 language:HTML' + \ ], 'Xtags') + call assert_fails('tj /foo', 'E426') + try + tj /foo + catch /^Vim\%((\a\+)\)\=:E431/ + call assert_report(v:exception) + catch /.*/ + endtry + call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) + call delete('Xtags') + let &verbose = old_vbs +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index 3ad2025915..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([ @@ -61,3 +61,39 @@ func Test_tags_too_long() call assert_fails('tag ' . repeat('x', 1020), 'E426') tags endfunc + +" For historical reasons we support a tags file where the last line is missing +" the newline. +func Test_tagsfile_without_trailing_newline() + call writefile(["Foo\tfoo\t1"], 'Xtags', 'b') + set tags=Xtags + + let tl = taglist('.*') + call assert_equal(1, len(tl)) + call assert_equal('Foo', tl[0].name) + + 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_textformat.vim b/src/nvim/testdir/test_textformat.vim index 999566c6ac..13fb50b985 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -163,6 +163,329 @@ func Test_text_format() \ '# 1 xxxxx', \ '# foobar'], getline(1, 2)) + " Test the 'p' flag for 'formatoptions' + " First test without the flag: that it will break "Mr. Feynman" at the space + normal ggdG + setl tw=28 fo=tcq + call setline('.', 'Surely you''re joking, Mr. Feynman!') + normal gqq + call assert_equal([ + \ 'Surely you''re joking, Mr.', + \ 'Feynman!'], getline(1, 2)) + " Now test with the flag: that it will push the name with the title onto the + " next line + normal ggdG + setl fo+=p + call setline('.', 'Surely you''re joking, Mr. Feynman!') + normal gqq + call assert_equal([ + \ 'Surely you''re joking,', + \ 'Mr. Feynman!'], getline(1, 2)) + " Ensure that it will still break if two spaces are entered + normal ggdG + call setline('.', 'Surely you''re joking, Mr. Feynman!') + normal gqq + call assert_equal([ + \ 'Surely you''re joking, Mr.', + \ 'Feynman!'], getline(1, 2)) + setl ai& tw& fo& si& comments& enew! endfunc + +" Tests for :right, :center and :left on text with embedded TAB. +func Test_format_align() + enew! + set tw=65 + + " :left alignment + call append(0, [ + \ " test for :left", + \ " a a", + \ " fa a", + \ " dfa a", + \ " sdfa a", + \ " asdfa a", + \ " xasdfa a", + \ "asxxdfa a", + \ ]) + %left + call assert_equal([ + \ "test for :left", + \ "a a", + \ "fa a", + \ "dfa a", + \ "sdfa a", + \ "asdfa a", + \ "xasdfa a", + \ "asxxdfa a", + \ "" + \ ], getline(1, '$')) + enew! + + " :center alignment + call append(0, [ + \ " test for :center", + \ " a a", + \ " fa afd asdf", + \ " dfa a", + \ " sdfa afd asdf", + \ " asdfa a", + \ " xasdfa asdfasdfasdfasdfasdf", + \ "asxxdfa a" + \ ]) + %center + call assert_equal([ + \ " test for :center", + \ " a a", + \ " fa afd asdf", + \ " dfa a", + \ " sdfa afd asdf", + \ " asdfa a", + \ " xasdfa asdfasdfasdfasdfasdf", + \ " asxxdfa a", + \ "" + \ ], getline(1, '$')) + enew! + + " :right alignment + call append(0, [ + \ " test for :right", + \ " a a", + \ " fa a", + \ " dfa a", + \ " sdfa a", + \ " asdfa a", + \ " xasdfa a", + \ " asxxdfa a", + \ " asxa;ofa a", + \ " asdfaqwer a", + \ " a ax", + \ " fa ax", + \ " dfa ax", + \ " sdfa ax", + \ " asdfa ax", + \ " xasdfa ax", + \ " asxxdfa ax", + \ " asxa;ofa ax", + \ " asdfaqwer ax", + \ " a axx", + \ " fa axx", + \ " dfa axx", + \ " sdfa axx", + \ " asdfa axx", + \ " xasdfa axx", + \ " asxxdfa axx", + \ " asxa;ofa axx", + \ " asdfaqwer axx", + \ " a axxx", + \ " fa axxx", + \ " dfa axxx", + \ " sdfa axxx", + \ " asdfa axxx", + \ " xasdfa axxx", + \ " asxxdfa axxx", + \ " asxa;ofa axxx", + \ " asdfaqwer axxx", + \ " a axxxo", + \ " fa axxxo", + \ " dfa axxxo", + \ " sdfa axxxo", + \ " asdfa axxxo", + \ " xasdfa axxxo", + \ " asxxdfa axxxo", + \ " asxa;ofa axxxo", + \ " asdfaqwer axxxo", + \ " a axxxoi", + \ " fa axxxoi", + \ " dfa axxxoi", + \ " sdfa axxxoi", + \ " asdfa axxxoi", + \ " xasdfa axxxoi", + \ " asxxdfa axxxoi", + \ " asxa;ofa axxxoi", + \ " asdfaqwer axxxoi", + \ " a axxxoik", + \ " fa axxxoik", + \ " dfa axxxoik", + \ " sdfa axxxoik", + \ " asdfa axxxoik", + \ " xasdfa axxxoik", + \ " asxxdfa axxxoik", + \ " asxa;ofa axxxoik", + \ " asdfaqwer axxxoik", + \ " a axxxoike", + \ " fa axxxoike", + \ " dfa axxxoike", + \ " sdfa axxxoike", + \ " asdfa axxxoike", + \ " xasdfa axxxoike", + \ " asxxdfa axxxoike", + \ " asxa;ofa axxxoike", + \ " asdfaqwer axxxoike", + \ " a axxxoikey", + \ " fa axxxoikey", + \ " dfa axxxoikey", + \ " sdfa axxxoikey", + \ " asdfa axxxoikey", + \ " xasdfa axxxoikey", + \ " asxxdfa axxxoikey", + \ " asxa;ofa axxxoikey", + \ " asdfaqwer axxxoikey", + \ ]) + %right + call assert_equal([ + \ "\t\t\t\t test for :right", + \ "\t\t\t\t a a", + \ "\t\t\t\t fa a", + \ "\t\t\t\t dfa a", + \ "\t\t\t\t sdfa a", + \ "\t\t\t\t asdfa a", + \ "\t\t\t\t xasdfa a", + \ "\t\t\t\t asxxdfa a", + \ "\t\t\t\t asxa;ofa a", + \ "\t\t\t\t asdfaqwer a", + \ "\t\t\t\t a ax", + \ "\t\t\t\t fa ax", + \ "\t\t\t\t dfa ax", + \ "\t\t\t\t sdfa ax", + \ "\t\t\t\t asdfa ax", + \ "\t\t\t\t xasdfa ax", + \ "\t\t\t\t asxxdfa ax", + \ "\t\t\t\t asxa;ofa ax", + \ "\t\t\t\t asdfaqwer ax", + \ "\t\t\t\t a axx", + \ "\t\t\t\t fa axx", + \ "\t\t\t\t dfa axx", + \ "\t\t\t\t sdfa axx", + \ "\t\t\t\t asdfa axx", + \ "\t\t\t\t xasdfa axx", + \ "\t\t\t\t asxxdfa axx", + \ "\t\t\t\t asxa;ofa axx", + \ "\t\t\t\t asdfaqwer axx", + \ "\t\t\t\t a axxx", + \ "\t\t\t\t fa axxx", + \ "\t\t\t\t dfa axxx", + \ "\t\t\t\t sdfa axxx", + \ "\t\t\t\t asdfa axxx", + \ "\t\t\t\t xasdfa axxx", + \ "\t\t\t\t asxxdfa axxx", + \ "\t\t\t\t asxa;ofa axxx", + \ "\t\t\t\t asdfaqwer axxx", + \ "\t\t\t\t a axxxo", + \ "\t\t\t\t fa axxxo", + \ "\t\t\t\t dfa axxxo", + \ "\t\t\t\t sdfa axxxo", + \ "\t\t\t\t asdfa axxxo", + \ "\t\t\t\t xasdfa axxxo", + \ "\t\t\t\t asxxdfa axxxo", + \ "\t\t\t\t asxa;ofa axxxo", + \ "\t\t\t\t asdfaqwer axxxo", + \ "\t\t\t\t a axxxoi", + \ "\t\t\t\t fa axxxoi", + \ "\t\t\t\t dfa axxxoi", + \ "\t\t\t\t sdfa axxxoi", + \ "\t\t\t\t asdfa axxxoi", + \ "\t\t\t\t xasdfa axxxoi", + \ "\t\t\t\t asxxdfa axxxoi", + \ "\t\t\t\t asxa;ofa axxxoi", + \ "\t\t\t\t asdfaqwer axxxoi", + \ "\t\t\t\t a axxxoik", + \ "\t\t\t\t fa axxxoik", + \ "\t\t\t\t dfa axxxoik", + \ "\t\t\t\t sdfa axxxoik", + \ "\t\t\t\t asdfa axxxoik", + \ "\t\t\t\t xasdfa axxxoik", + \ "\t\t\t\t asxxdfa axxxoik", + \ "\t\t\t\t asxa;ofa axxxoik", + \ "\t\t\t\t asdfaqwer axxxoik", + \ "\t\t\t\t a axxxoike", + \ "\t\t\t\t fa axxxoike", + \ "\t\t\t\t dfa axxxoike", + \ "\t\t\t\t sdfa axxxoike", + \ "\t\t\t\t asdfa axxxoike", + \ "\t\t\t\t xasdfa axxxoike", + \ "\t\t\t\t asxxdfa axxxoike", + \ "\t\t\t\t asxa;ofa axxxoike", + \ "\t\t\t\t asdfaqwer axxxoike", + \ "\t\t\t\t a axxxoikey", + \ "\t\t\t\t fa axxxoikey", + \ "\t\t\t\t dfa axxxoikey", + \ "\t\t\t\t sdfa axxxoikey", + \ "\t\t\t\t asdfa axxxoikey", + \ "\t\t\t\t xasdfa axxxoikey", + \ "\t\t\t\t asxxdfa axxxoikey", + \ "\t\t\t\t asxa;ofa axxxoikey", + \ "\t\t\t\t asdfaqwer axxxoikey", + \ "" + \ ], getline(1, '$')) + enew! + + set tw& +endfunc + +" Test formatting a paragraph. +func Test_format_para() + enew! + set fo+=tcroql tw=72 + + call append(0, [ + \ "xxxxx xx xxxxxx ", + \ "xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx", + \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx", + \ "xx xxxxxxx. xxxx xxxx.", + \ "", + \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx", + \ "> xxxxxx xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx" + \ ]) + exe "normal /xxxxxxxx$\<CR>" + normal 0gq6kk + call assert_equal([ + \ "xxxxx xx xxxxxx xxxxxxx xxxxxxxxx xxx xxxx xxxxx xxxxx xxx xx", + \ "xxxxxxxxxxxxxxxxxx xxxxx xxxx, xxxx xxxx xxxx xxxx xxx xx xx xx xxxxxxx.", + \ "xxxx xxxx.", + \ "", + \ "> xx xx, xxxx xxxx xxx xxxx xxx xxxxx xxx xxx xxxxxxx xxx xxxxx xxxxxx", + \ "> xxxxxxx: xxxx xxxxxxx, xx xxxxxx xxxx xxxxxxxxxx", + \ "" + \ ], getline(1, '$')) + + set fo& tw& + enew! +endfunc + +" Test undo after ":%s" and formatting. +func Test_format_undo() + enew! + map gg :.,.+2s/^/x/<CR>kk:set tw=3<CR>gqq + + call append(0, [ + \ "aa aa aa aa", + \ "bb bb bb bb", + \ "cc cc cc cc" + \ ]) + " undo/redo here to make the next undo only work on the following changes + exe "normal i\<C-G>u" + call cursor(1,1) + normal ggu + call assert_equal([ + \ "aa aa aa aa", + \ "bb bb bb bb", + \ "cc cc cc cc", + \ "" + \ ], getline(1, '$')) + + unmap gg + set tw& + enew! +endfunc + +func Test_format_list_auto() + new + call setline(1, ['1. abc', '2. def', '3. ghi']) + set fo=tan ai bs=2 + call feedkeys("3G0lli\<BS>\<BS>x\<Esc>", 'tx') + call assert_equal('2. defx ghi', getline(2)) + bwipe! + set fo& ai& bs& +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 62ddad5dce..9384989a35 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -15,17 +15,13 @@ func MyHandlerWithLists(lists, timer) let x = string(a:lists) endfunc -func s:assert_inrange(lower, upper, actual) - return assert_inrange(a:lower, LoadAdjust(a:upper), a:actual) -endfunc - func Test_oneshot() let g:val = 0 let timer = timer_start(50, 'MyHandler') let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call s:assert_inrange(40, 120, slept) + call assert_inrange(40, LoadAdjust(120), slept) else call assert_inrange(20, 120, slept) endif @@ -37,7 +33,7 @@ func Test_repeat_three() let slept = WaitFor('g:val == 3') call assert_equal(3, g:val) if has('reltime') - call s:assert_inrange(120, 250, slept) + call assert_inrange(120, LoadAdjust(250), slept) else call assert_inrange(80, 200, slept) endif @@ -47,9 +43,12 @@ func Test_repeat_many() call timer_stopall() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': -1}) + if has('mac') + sleep 200m + endif sleep 200m call timer_stop(timer) - call assert_inrange((has('mac') ? 1 : 2), 4, g:val) + call assert_inrange((has('mac') ? 1 : 2), LoadAdjust(4), g:val) endfunc func Test_with_partial_callback() @@ -63,7 +62,7 @@ func Test_with_partial_callback() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call s:assert_inrange(40, 130, slept) + call assert_inrange(40, LoadAdjust(130), slept) else call assert_inrange(20, 100, slept) endif @@ -126,7 +125,7 @@ func Test_paused() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call s:assert_inrange(0, 140, slept) + call assert_inrange(0, LoadAdjust(140), slept) else call assert_inrange(0, 10, slept) endif @@ -197,6 +196,23 @@ func Test_input_in_timer() call assert_equal('hello', g:val) endfunc +func FuncWithError(timer) + let g:call_count += 1 + if g:call_count == 4 + return + endif + doesnotexist +endfunc + +func Test_timer_errors() + let g:call_count = 0 + let timer = timer_start(10, 'FuncWithError', {'repeat': -1}) + " Timer will be stopped after failing 3 out of 3 times. + call WaitFor('g:call_count == 3') + sleep 50m + call assert_equal(3, g:call_count) +endfunc + func FuncWithCaughtError(timer) let g:call_count += 1 try diff --git a/src/nvim/testdir/test_true_false.vim b/src/nvim/testdir/test_true_false.vim index 4a5d47471d..ad865bb113 100644 --- a/src/nvim/testdir/test_true_false.vim +++ b/src/nvim/testdir/test_true_false.vim @@ -134,6 +134,8 @@ func Test_non_zero_arg() " call test_settime(93784) " call Try_arg_non_zero("mode(%v%)", 'x', 'x!') " call test_settime(0) + let shellslash = &shellslash + set shellslash call Try_arg_non_zero("shellescape('foo%', %v%)", "'foo%'", "'foo\\%'") @@ -152,4 +154,6 @@ func Test_non_zero_arg() let r = visualmode(v) call assert_equal('', r, 'result for ' . v . ' is not "" but ' . r) endfor + + let &shellslash = shellslash endfunc diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 9729ca9f57..0cb5dc4033 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! @@ -388,6 +389,15 @@ funct Test_undofile() " Test undofile() with 'undodir' set to a non-existing directory. " call assert_equal('', undofile('Xundofoo')) + if isdirectory('/tmp') + set undodir=/tmp + if has('osx') + call assert_equal('/tmp/%private%tmp%file', undofile('///tmp/file')) + else + call assert_equal('/tmp/%tmp%file', undofile('///tmp/file')) + endif + endif + set undodir& endfunc 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/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index e4618610cd..ee43540fdd 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -158,3 +158,29 @@ func Test_winfixwidth_on_close() %bwipeout! setlocal nowinfixwidth splitbelow& splitright& endfunction + +" Test that 'winfixheight' will be respected even there is non-leaf frame +fun! Test_winfixheight_non_leaf_frame() + vsplit + botright 11new + let l:wid = win_getid() + setlocal winfixheight + call assert_equal(11, winheight(l:wid)) + botright new + bwipe! + call assert_equal(11, winheight(l:wid)) + %bwipe! +endf + +" Test that 'winfixwidth' will be respected even there is non-leaf frame +fun! Test_winfixwidth_non_leaf_frame() + split + topleft 11vnew + let l:wid = win_getid() + setlocal winfixwidth + call assert_equal(11, winwidth(l:wid)) + topleft new + bwipe! + call assert_equal(11, winwidth(l:wid)) + %bwipe! +endf diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 57fb36abb8..003a23ea7b 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -646,4 +646,54 @@ func Test_relative_cursor_second_line_after_resize() let &so = so_save endfunc +" Tests for the winnr() function +func Test_winnr() + only | tabonly + call assert_equal(1, winnr('j')) + call assert_equal(1, winnr('k')) + call assert_equal(1, winnr('h')) + call assert_equal(1, winnr('l')) + + " create a set of horizontally and vertically split windows + leftabove new | wincmd p + leftabove new | wincmd p + rightbelow new | wincmd p + rightbelow new | wincmd p + leftabove vnew | wincmd p + leftabove vnew | wincmd p + rightbelow vnew | wincmd p + rightbelow vnew | wincmd p + + call assert_equal(8, winnr('j')) + call assert_equal(2, winnr('k')) + call assert_equal(4, winnr('h')) + call assert_equal(6, winnr('l')) + call assert_equal(9, winnr('2j')) + call assert_equal(1, winnr('2k')) + call assert_equal(3, winnr('2h')) + call assert_equal(7, winnr('2l')) + + " Error cases + call assert_fails("echo winnr('0.2k')", 'E15:') + call assert_equal(2, winnr('-2k')) + call assert_fails("echo winnr('-2xj')", 'E15:') + call assert_fails("echo winnr('j2j')", 'E15:') + call assert_fails("echo winnr('ll')", 'E15:') + call assert_fails("echo winnr('5')", 'E15:') + call assert_equal(4, winnr('0h')) + + tabnew + call assert_equal(8, tabpagewinnr(1, 'j')) + call assert_equal(2, tabpagewinnr(1, 'k')) + call assert_equal(4, tabpagewinnr(1, 'h')) + call assert_equal(6, tabpagewinnr(1, 'l')) + + only | tabonly +endfunc + +func Test_window_colon_command() + " This was reading invalid memory. + exe "norm! v\<C-W>:\<C-U>echo v:version" +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 1cd5fb306a..b4585a72ef 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -36,13 +36,15 @@ func Test_writefile_fails_conversion() if !has('multi_byte') || !has('iconv') return endif + " Without a backup file the write won't happen if there is a conversion + " error. set nobackup nowritebackup new let contents = ["line one", "line two"] call writefile(contents, 'Xfile') edit Xfile call setline(1, ["first line", "cannot convert \u010b", "third line"]) - call assert_fails('write ++enc=cp932') + call assert_fails('write ++enc=cp932', 'E513:') call assert_equal(contents, readfile('Xfile')) call delete('Xfile') @@ -50,6 +52,27 @@ func Test_writefile_fails_conversion() set backup& writebackup& endfunc +func Test_writefile_fails_conversion2() + if !has('iconv') || has('sun') + return + endif + " With a backup file the write happens even if there is a conversion error, + " but then the backup file must remain + set nobackup writebackup + let contents = ["line one", "line two"] + call writefile(contents, 'Xfile_conversion_err') + edit Xfile_conversion_err + call setline(1, ["first line", "cannot convert \u010b", "third line"]) + set fileencoding=latin1 + let output = execute('write') + call assert_match('CONVERSION ERROR', output) + call assert_equal(contents, readfile('Xfile_conversion_err~')) + + call delete('Xfile_conversion_err') + call delete('Xfile_conversion_err~') + bwipe! +endfunc + func SetFlag(timer) let g:flag = 1 endfunc diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 3eb88366d6..6d9023bd79 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -10,6 +10,7 @@ #include "nvim/charset.h" #include "nvim/main.h" #include "nvim/aucmd.h" +#include "nvim/ex_docmd.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -22,7 +23,7 @@ # include "tui/input.c.generated.h" #endif -void term_input_init(TermInput *input, Loop *loop) +void tinput_init(TermInput *input, Loop *loop) { input->loop = loop; input->paste_enabled = false; @@ -48,20 +49,12 @@ void term_input_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); } -void term_input_destroy(TermInput *input) +void tinput_destroy(TermInput *input) { rbuffer_free(input->key_buffer); uv_mutex_destroy(&input->key_buffer_mutex); @@ -71,23 +64,23 @@ void term_input_destroy(TermInput *input) termkey_destroy(input->tk); } -void term_input_start(TermInput *input) +void tinput_start(TermInput *input) { - rstream_start(&input->read_stream, read_cb, input); + rstream_start(&input->read_stream, tinput_read_cb, input); } -void term_input_stop(TermInput *input) +void tinput_stop(TermInput *input) { rstream_stop(&input->read_stream); time_watcher_stop(&input->timer_handle); } -static void input_done_event(void **argv) +static void tinput_done_event(void **argv) { input_done(); } -static void wait_input_enqueue(void **argv) +static void tinput_wait_enqueue(void **argv) { TermInput *input = argv[0]; RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { @@ -106,12 +99,12 @@ static void wait_input_enqueue(void **argv) uv_mutex_unlock(&input->key_buffer_mutex); } -static void flush_input(TermInput *input, bool wait_until_empty) +static void tinput_flush(TermInput *input, bool wait_until_empty) { size_t drain_boundary = wait_until_empty ? 0 : 0xff; do { uv_mutex_lock(&input->key_buffer_mutex); - loop_schedule(&main_loop, event_create(wait_input_enqueue, 1, input)); + loop_schedule(&main_loop, event_create(tinput_wait_enqueue, 1, input)); input->waiting = true; while (input->waiting) { uv_cond_wait(&input->key_buffer_cond, &input->key_buffer_mutex); @@ -120,13 +113,13 @@ static void flush_input(TermInput *input, bool wait_until_empty) } while (rbuffer_size(input->key_buffer) > drain_boundary); } -static void enqueue_input(TermInput *input, char *buf, size_t size) +static void tinput_enqueue(TermInput *input, char *buf, size_t size) { if (rbuffer_size(input->key_buffer) > rbuffer_capacity(input->key_buffer) - 0xff) { // don't ever let the buffer get too full or we risk putting incomplete keys // into it - flush_input(input, false); + tinput_flush(input, false); } rbuffer_write(input->key_buffer, buf, size); } @@ -146,7 +139,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key) ptr++; } - enqueue_input(input, buf, len); + tinput_enqueue(input, buf, len); } static void forward_modified_utf8(TermInput *input, TermKeyKey *key) @@ -164,7 +157,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); } - enqueue_input(input, buf, len); + tinput_enqueue(input, buf, len); } static void forward_mouse_event(TermInput *input, TermKeyKey *key) @@ -236,7 +229,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) } len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); - enqueue_input(input, buf, len); + tinput_enqueue(input, buf, len); } static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) @@ -244,7 +237,7 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key); } -static void timer_cb(TimeWatcher *watcher, void *data); +static void tinput_timer_cb(TimeWatcher *watcher, void *data); static int get_key_code_timeout(void) { @@ -287,16 +280,16 @@ static void tk_getkeys(TermInput *input, bool force) if (ms > 0) { // Stop the current timer if already running time_watcher_stop(&input->timer_handle); - time_watcher_start(&input->timer_handle, timer_cb, (uint32_t)ms, 0); + time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0); } else { tk_getkeys(input, true); } } -static void timer_cb(TimeWatcher *watcher, void *data) +static void tinput_timer_cb(TimeWatcher *watcher, void *data) { tk_getkeys(data, true); - flush_input(data, true); + tinput_flush(data, true); } /// Handle focus events. @@ -332,7 +325,7 @@ static bool handle_bracketed_paste(TermInput *input) if (input->paste_enabled == enable) { return true; } - enqueue_input(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1); + tinput_enqueue(input, PASTETOGGLE_KEY, sizeof(PASTETOGGLE_KEY) - 1); input->paste_enabled = enable; return true; } @@ -357,15 +350,17 @@ static bool handle_forced_escape(TermInput *input) static void set_bg_deferred(void **argv) { char *bgvalue = argv[0]; - if (starting) { - // Wait until after startup, so OptionSet is triggered. - loop_schedule(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); - return; - } if (!option_was_set("bg") && !strequal((char *)p_bg, bgvalue)) { // Value differs, apply it. - set_option_value("bg", 0L, bgvalue, 0); - reset_option_was_set("bg"); + if (starting) { + // Wait until after startup, so OptionSet is triggered. + do_cmdline_cmd((bgvalue[0] == 'l') + ? "autocmd VimEnter * ++once ++nested set bg=light" + : "autocmd VimEnter * ++once ++nested set bg=dark"); + } else { + set_option_value("bg", 0L, bgvalue, 0); + reset_option_was_set("bg"); + } } } @@ -424,7 +419,8 @@ static bool handle_background_color(TermInput *input) double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 char *bgvalue = luminance < 0.5 ? "dark" : "light"; DLOG("bg response: %s", bgvalue); - loop_schedule(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); + loop_schedule_deferred(&main_loop, + event_create(set_bg_deferred, 1, bgvalue)); } else { DLOG("failed to parse bg response"); } @@ -433,8 +429,8 @@ static bool handle_background_color(TermInput *input) return false; } -static void read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, - bool eof) +static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, + void *data, bool eof) { TermInput *input = data; @@ -452,9 +448,10 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, // ls *.md | xargs nvim input->in_fd = 2; stream_close(&input->read_stream, NULL, NULL); - multiqueue_put(input->loop->fast_events, restart_reading, 1, input); + multiqueue_put(input->loop->fast_events, tinput_restart_reading, 1, + input); } else { - loop_schedule(&main_loop, event_create(input_done_event, 0)); + loop_schedule(&main_loop, event_create(tinput_done_event, 0)); } return; } @@ -494,15 +491,15 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, } } } while (rbuffer_size(input->read_stream.buffer)); - flush_input(input, true); + tinput_flush(input, true); // Make sure the next input escape sequence fits into the ring buffer // without wrap around, otherwise it could be misinterpreted. rbuffer_reset(input->read_stream.buffer); } -static void restart_reading(void **argv) +static void tinput_restart_reading(void **argv) { TermInput *input = argv[0]; rstream_init_fd(input->loop, &input->read_stream, input->in_fd, 0xfff); - rstream_start(&input->read_stream, read_cb, input); + rstream_start(&input->read_stream, tinput_read_cb, 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 3e7a3b1ba1..42e5b9b270 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -342,7 +342,7 @@ static void tui_terminal_start(UI *ui) terminfo_start(ui); tui_guess_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); - term_input_start(&data->input); + tinput_start(&data->input); } static void tui_terminal_after_startup(UI *ui) @@ -364,7 +364,7 @@ static void tui_terminal_stop(UI *ui) ui->data = NULL; // Flag UI as "stopped". return; } - term_input_stop(&data->input); + tinput_stop(&data->input); signal_watcher_stop(&data->winch_handle); terminfo_stop(ui); ugrid_free(&data->grid); @@ -404,7 +404,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 data->input.tk_ti_hook_fn = tui_tk_ti_getstr; #endif - term_input_init(&data->input, &tui_loop); + tinput_init(&data->input, &tui_loop); tui_terminal_start(ui); // Allow main thread to continue, we are ready to handle UI callbacks. @@ -429,7 +429,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui) } ui_bridge_stopped(bridge); - term_input_destroy(&data->input); + tinput_destroy(&data->input); signal_watcher_stop(&data->cont_handle); signal_watcher_close(&data->cont_handle, NULL); signal_watcher_close(&data->winch_handle, NULL); @@ -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 d07ea3179e..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])); @@ -460,7 +473,9 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) if (wp->w_floating) { if (width != wp->w_width && height != wp->w_height) { - win_config_float(wp, (int)width, (int)height, wp->w_float_config); + wp->w_float_config.width = width; + wp->w_float_config.height = height; + win_config_float(wp, wp->w_float_config); } } else { // non-positive indicates no request 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/ui_compositor.c b/src/nvim/ui_compositor.c index 9ad3f851ad..e24ab11a3a 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -3,6 +3,8 @@ // Compositor: merge floating grids with the main grid for display in // TUI and non-multigrid UIs. +// +// Layer-based compositing: https://en.wikipedia.org/wiki/Digital_compositing #include <assert.h> #include <stdbool.h> @@ -104,6 +106,9 @@ bool ui_comp_should_draw(void) return composed_uis != 0 && valid_screen; } +/// Places `grid` at (col,row) position with (width * height) size. +/// Adds `grid` as the top layer if it is a new layer. +/// /// TODO(bfredl): later on the compositor should just use win_float_pos events, /// though that will require slight event order adjustment: emit the win_pos /// events in the beginning of update_screen(0), rather than in ui_flush() @@ -158,6 +163,7 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, if (insert_at < kv_size(layers)-1) { for (size_t i = kv_size(layers)-1; i > insert_at; i--) { kv_A(layers, i) = kv_A(layers, i-1); + kv_A(layers, i)->comp_index = i; } kv_A(layers, insert_at) = grid; } diff --git a/src/nvim/ui_compositor.h b/src/nvim/ui_compositor.h index b3780db532..70e01c3a21 100644 --- a/src/nvim/ui_compositor.h +++ b/src/nvim/ui_compositor.h @@ -1,5 +1,3 @@ -// Bridge for communication between a UI thread and nvim core. -// Used by the built-in TUI and libnvim-based UIs. #ifndef NVIM_UI_COMPOSITOR_H #define NVIM_UI_COMPOSITOR_H diff --git a/src/nvim/undo.c b/src/nvim/undo.c index df0507ed41..10996d99d5 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -83,13 +83,11 @@ #include <string.h> #include <fcntl.h> -#include "nvim/vim.h" +#include "nvim/buffer.h" #include "nvim/ascii.h" #include "nvim/undo.h" -#include "nvim/macros.h" #include "nvim/cursor.h" #include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/buffer_updates.h" @@ -102,8 +100,6 @@ #include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/quickfix.h" -#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -1135,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; } @@ -1822,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; @@ -2154,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 @@ -2221,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) { @@ -2232,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; @@ -2249,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]); } @@ -2452,7 +2450,9 @@ static void u_undo_end( } } - smsg(_("%" PRId64 " %s; %s #%" PRId64 " %s"), + smsg_attr_keep( + 0, + _("%" PRId64 " %s; %s #%" PRId64 " %s"), u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount, _(msgstr), did_undo ? _("before") : _("after"), @@ -2585,9 +2585,13 @@ static void u_add_time(char_u *buf, size_t buflen, time_t tt) else /* longer ago */ (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); - } else - vim_snprintf((char *)buf, buflen, _("%" PRId64 " seconds ago"), - (int64_t)(time(NULL) - tt)); + } else { + int64_t seconds = time(NULL) - tt; + vim_snprintf((char *)buf, buflen, + NGETTEXT("%" PRId64 " second ago", + "%" PRId64 " seconds ago", (uint32_t)seconds), + seconds); + } } /* diff --git a/src/nvim/version.c b/src/nvim/version.c index b7c9140b7f..b18cfc6dde 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -158,7 +158,7 @@ static const int included_patches[] = { 1766, 1765, 1764, - // 1763, + 1763, // 1762, // 1761, 1760, @@ -393,7 +393,7 @@ static const int included_patches[] = { // 1531, 1530, // 1529, - // 1528, + 1528, // 1527, // 1526, // 1525, @@ -474,7 +474,7 @@ static const int included_patches[] = { 1450, // 1449, // 1448, - // 1447, + 1447, 1446, 1445, 1444, @@ -549,7 +549,7 @@ static const int included_patches[] = { // 1375, 1374, 1373, - // 1372, + 1372, // 1371, 1370, 1369, @@ -585,7 +585,7 @@ static const int included_patches[] = { // 1339, 1338, 1337, - // 1336, + 1336, // 1335, // 1334, 1333, @@ -768,7 +768,7 @@ static const int included_patches[] = { 1156, 1155, 1154, - // 1153, + 1153, 1152, 1151, 1150, @@ -807,7 +807,7 @@ static const int included_patches[] = { // 1117, // 1116, 1115, - // 1114, + 1114, // 1113, // 1112, 1111, @@ -844,11 +844,11 @@ static const int included_patches[] = { // 1080, // 1079, 1078, - // 1077, + 1077, // 1076, // 1075, // 1074, - // 1073, + 1073, 1072, 1071, // 1070, @@ -876,7 +876,7 @@ static const int included_patches[] = { 1048, 1047, 1046, - // 1045, + 1045, 1044, 1043, 1042, @@ -1193,7 +1193,7 @@ static const int included_patches[] = { 731, // 730, 729, - // 728, + 728, 727, 726, // 725, @@ -1212,11 +1212,11 @@ static const int included_patches[] = { // 712, 711, 710, - // 709, + 709, 708, 707, 706, - // 705, + 705, 704, 703, // 702, @@ -1240,7 +1240,7 @@ static const int included_patches[] = { 684, // 683, 682, - // 681, + 681, 680, 679, 678, @@ -1275,10 +1275,10 @@ static const int included_patches[] = { 649, 648, // 647, - // 646, - // 645, - // 644, - // 643, + 646, + 645, + 644, + 643, 642, 641, 640, @@ -1292,7 +1292,7 @@ static const int included_patches[] = { 632, 631, 630, - // 629, + 629, 628, 627, 626, @@ -1329,7 +1329,7 @@ static const int included_patches[] = { 595, 594, 593, - // 592, + 592, 591, 590, 589, @@ -1349,7 +1349,7 @@ static const int included_patches[] = { 575, 574, 573, - // 572, + 572, 571, 570, 569, @@ -1415,9 +1415,9 @@ static const int included_patches[] = { 509, 508, 507, - // 506, + 506, 505, - // 504, + 504, 503, 502, 501, @@ -1508,9 +1508,9 @@ static const int included_patches[] = { 416, 415, 414, - // 413, - // 412, - // 411, + 413, + 412, + 411, 410, 409, 408, 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 83ddf534cf..6bc082ffb2 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -502,10 +502,11 @@ wingotofile: break; } FloatConfig config = FLOAT_CONFIG_INIT; + config.width = curwin->w_width; + config.height = curwin->w_height; config.external = true; Error err = ERROR_INIT; - if (!win_new_float(curwin, curwin->w_width, curwin->w_height, config, - &err)) { + if (!win_new_float(curwin, config, &err)) { EMSG(err.msg); api_clear_error(&err); beep_flush(); @@ -538,12 +539,9 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, /// float. It must then already belong to the current tabpage! /// /// config must already have been validated! -win_T *win_new_float(win_T *wp, int width, int height, FloatConfig config, - Error *err) +win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) { - bool new = false; if (wp == NULL) { - new = true; wp = win_alloc(lastwin_nofloating(), false); win_init(wp, curwin, 0); } else { @@ -569,29 +567,30 @@ win_T *win_new_float(win_T *wp, int width, int height, FloatConfig config, wp->w_floating = 1; wp->w_status_height = 0; wp->w_vsep_width = 0; - win_config_float(wp, width, height, config); + + // TODO(bfredl): use set_option_to() after merging #9110 ? + wp->w_p_nu = false; + wp->w_allbuf_opt.wo_nu = false; + win_config_float(wp, fconfig); wp->w_pos_changed = true; redraw_win_later(wp, VALID); - if (new) { - win_enter(wp, false); - } return wp; } -void win_config_float(win_T *wp, int width, int height, - FloatConfig config) +void win_config_float(win_T *wp, FloatConfig fconfig) { - wp->w_height = MAX(height, 1); - wp->w_width = MAX(width, 2); + wp->w_width = MAX(fconfig.width, 1); + wp->w_height = MAX(fconfig.height, 1); - if (config.relative == kFloatRelativeCursor) { - config.relative = kFloatRelativeWindow; - config.row += curwin->w_wrow; - config.col += curwin->w_wcol; - config.window = curwin->handle; + if (fconfig.relative == kFloatRelativeCursor) { + fconfig.relative = kFloatRelativeWindow; + fconfig.row += curwin->w_wrow; + fconfig.col += curwin->w_wcol; + fconfig.window = curwin->handle; } - wp->w_float_config = config; + bool change_external = fconfig.external != wp->w_float_config.external; + wp->w_float_config = fconfig; if (!ui_has(kUIMultigrid)) { wp->w_height = MIN(wp->w_height, Rows-1); @@ -601,6 +600,10 @@ void win_config_float(win_T *wp, int width, int height, win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); wp->w_pos_changed = true; + if (change_external) { + wp->w_hl_needs_update = true; + redraw_win_later(wp, NOT_VALID); + } } static void ui_ext_win_position(win_T *wp) @@ -610,28 +613,25 @@ static void ui_ext_win_position(win_T *wp) wp->w_wincol, wp->w_width, wp->w_height); return; } - const char *const anchor_str[] = { - "NW", - "NE", - "SW", - "SE" - }; FloatConfig c = wp->w_float_config; if (!c.external) { ScreenGrid *grid = &default_grid; - int row = c.row, col = c.col; + float row = c.row, col = c.col; if (c.relative == kFloatRelativeWindow) { Error dummy = ERROR_INIT; win_T *win = find_window_by_handle(c.window, &dummy); if (win) { grid = &win->w_grid; - screen_adjust_grid(&grid, &row, &col); + int row_off = 0, col_off = 0; + screen_adjust_grid(&grid, &row_off, &col_off); + row += row_off; + col += col_off; } api_clear_error(&dummy); } if (ui_has(kUIMultigrid)) { - String anchor = cstr_to_string(anchor_str[c.anchor]); + String anchor = cstr_to_string(float_anchor_str[c.anchor]); ui_call_win_float_pos(wp->w_grid.handle, wp->handle, anchor, grid->handle, row, col, c.focusable); } else { @@ -640,16 +640,16 @@ static void ui_ext_win_position(win_T *wp) bool east = c.anchor & kFloatAnchorEast; bool south = c.anchor & kFloatAnchorSouth; - row -= (south ? wp->w_height : 0); - col -= (east ? wp->w_width : 0); - row = MAX(MIN(row, Rows-wp->w_height-1), 0); - col = MAX(MIN(col, Columns-wp->w_width), 0); - wp->w_winrow = row; - wp->w_wincol = col; + int comp_row = (int)row - (south ? wp->w_height : 0); + int comp_col = (int)col - (east ? wp->w_width : 0); + comp_row = MAX(MIN(comp_row, Rows-wp->w_height-1), 0); + comp_col = MAX(MIN(comp_col, Columns-wp->w_width), 0); + wp->w_winrow = comp_row; + wp->w_wincol = comp_col; bool valid = (wp->w_redr_type == 0); bool on_top = (curwin == wp) || !curwin->w_floating; - ui_comp_put_grid(&wp->w_grid, row, col, wp->w_height, wp->w_width, - valid, on_top); + ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, + wp->w_width, valid, on_top); if (!valid) { wp->w_grid.valid = false; redraw_win_later(wp, NOT_VALID); @@ -668,14 +668,14 @@ static bool parse_float_anchor(String anchor, FloatAnchor *out) *out = (FloatAnchor)0; } char *str = anchor.data; - if (!STRICMP(str, "NW")) { - *out = kFloatAnchorNW; - } else if (!STRICMP(str, "NE")) { - *out = kFloatAnchorNE; - } else if (!STRICMP(str, "SW")) { - *out = kFloatAnchorSW; - } else if (!STRICMP(str, "SE")) { - *out = kFloatAnchorSE; + if (striequal(str, "NW")) { + *out = 0; // NW is the default + } else if (striequal(str, "NE")) { + *out = kFloatAnchorEast; + } else if (striequal(str, "SW")) { + *out = kFloatAnchorSouth; + } else if (striequal(str, "SE")) { + *out = kFloatAnchorSouth | kFloatAnchorEast; } else { return false; } @@ -684,15 +684,12 @@ static bool parse_float_anchor(String anchor, FloatAnchor *out) static bool parse_float_relative(String relative, FloatRelative *out) { - if (relative.size == 0) { - *out = (FloatRelative)0; - } char *str = relative.data; - if (!STRICMP(str, "editor")) { + if (striequal(str, "editor")) { *out = kFloatRelativeEditor; - } else if (!STRICMP(str, "win")) { + } else if (striequal(str, "win")) { *out = kFloatRelativeWindow; - } else if (!STRICMP(str, "cursor")) { + } else if (striequal(str, "cursor")) { *out = kFloatRelativeCursor; } else { return false; @@ -700,11 +697,14 @@ static bool parse_float_relative(String relative, FloatRelative *out) return true; } -bool parse_float_config(Dictionary config, FloatConfig *out, bool reconf, +bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, Error *err) { + // TODO(bfredl): use a get/has_key interface instead and get rid of extra + // flags bool has_row = false, has_col = false, has_relative = false; bool has_external = false, has_window = false; + bool has_width = false, has_height = false; for (size_t i = 0; i < config.size; i++) { char *key = config.items[i].key.data; @@ -712,109 +712,137 @@ bool parse_float_config(Dictionary config, FloatConfig *out, bool reconf, if (!strcmp(key, "row")) { has_row = true; if (val.type == kObjectTypeInteger) { - out->row = val.data.integer; + fconfig->row = val.data.integer; } else if (val.type == kObjectTypeFloat) { - out->row = val.data.floating; + fconfig->row = val.data.floating; } else { api_set_error(err, kErrorTypeValidation, - "'row' option has to be Integer or Float"); + "'row' key must be Integer or Float"); return false; } } else if (!strcmp(key, "col")) { has_col = true; if (val.type == kObjectTypeInteger) { - out->col = val.data.integer; + fconfig->col = val.data.integer; } else if (val.type == kObjectTypeFloat) { - out->col = val.data.floating; + fconfig->col = val.data.floating; + } else { + api_set_error(err, kErrorTypeValidation, + "'col' key must be Integer or Float"); + return false; + } + } else if (strequal(key, "width")) { + has_width = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->width = val.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "'width' key must be a positive Integer"); + return false; + } + } else if (strequal(key, "height")) { + has_height = true; + if (val.type == kObjectTypeInteger && val.data.integer > 0) { + fconfig->height= val.data.integer; } else { api_set_error(err, kErrorTypeValidation, - "'col' option has to be Integer or Float"); + "'height' key must be a positive Integer"); return false; } } else if (!strcmp(key, "anchor")) { if (val.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, - "'anchor' option has to be String"); + "'anchor' key must be String"); return false; } - if (!parse_float_anchor(val.data.string, &out->anchor)) { + if (!parse_float_anchor(val.data.string, &fconfig->anchor)) { api_set_error(err, kErrorTypeValidation, - "Invalid value of 'anchor' option"); + "Invalid value of 'anchor' key"); return false; } } else if (!strcmp(key, "relative")) { - has_relative = true; if (val.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, - "'relative' option has to be String"); + "'relative' key must be String"); return false; } - if (!parse_float_relative(val.data.string, &out->relative)) { - api_set_error(err, kErrorTypeValidation, - "Invalid value of 'relative' option"); - return false; + // ignore empty string, to match nvim_win_get_config + if (val.data.string.size > 0) { + has_relative = true; + if (!parse_float_relative(val.data.string, &fconfig->relative)) { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'relative' key"); + return false; + } } } else if (!strcmp(key, "win")) { has_window = true; if (val.type != kObjectTypeInteger && val.type != kObjectTypeWindow) { api_set_error(err, kErrorTypeValidation, - "'win' option has to be Integer or Window"); + "'win' key must be Integer or Window"); return false; } - out->window = val.data.integer; + fconfig->window = val.data.integer; } else if (!strcmp(key, "external")) { if (val.type == kObjectTypeInteger) { - out->external = val.data.integer; + fconfig->external = val.data.integer; } else if (val.type == kObjectTypeBoolean) { - out->external = val.data.boolean; + fconfig->external = val.data.boolean; } else { api_set_error(err, kErrorTypeValidation, - "'external' option has to be Boolean"); + "'external' key must be Boolean"); return false; } - has_external = out->external; + has_external = fconfig->external; } else if (!strcmp(key, "focusable")) { if (val.type == kObjectTypeInteger) { - out->focusable = val.data.integer; + fconfig->focusable = val.data.integer; } else if (val.type == kObjectTypeBoolean) { - out->focusable = val.data.boolean; + fconfig->focusable = val.data.boolean; } else { api_set_error(err, kErrorTypeValidation, - "'focusable' option has to be Boolean"); + "'focusable' key must be Boolean"); return false; } } else { api_set_error(err, kErrorTypeValidation, - "Invalid options key '%s'", key); + "Invalid key '%s'", key); return false; } } - if (has_window && !(has_relative && out->relative == kFloatRelativeWindow)) { + if (has_window && !(has_relative + && fconfig->relative == kFloatRelativeWindow)) { api_set_error(err, kErrorTypeValidation, - "'win' option is only valid with relative='win'"); + "'win' key is only valid with relative='win'"); return false; } - if ((has_relative && out->relative == kFloatRelativeWindow) - && (!has_window || out->window == 0)) { - out->window = curwin->handle; + if ((has_relative && fconfig->relative == kFloatRelativeWindow) + && (!has_window || fconfig->window == 0)) { + fconfig->window = curwin->handle; } if (has_relative && has_external) { api_set_error(err, kErrorTypeValidation, - "Only one of 'relative' and 'external' should be used"); + "Only one of 'relative' and 'external' must be used"); return false; } else if (!reconf && !has_relative && !has_external) { api_set_error(err, kErrorTypeValidation, "One of 'relative' and 'external' must be used"); return false; } else if (has_relative) { - out->external = false; + fconfig->external = false; } - if (out->external && !ui_has(kUIMultigrid)) { + if (!reconf && !(has_height && has_width)) { + api_set_error(err, kErrorTypeValidation, + "Must specify 'width' and 'height'"); + return false; + } + + if (fconfig->external && !ui_has(kUIMultigrid)) { api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows"); return false; @@ -1121,6 +1149,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } else if (wp->w_floating) { new_frame(wp); wp->w_floating = false; + // non-floating window doesn't store float config. + wp->w_float_config = FLOAT_CONFIG_INIT; } /* @@ -2265,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; } @@ -2299,10 +2328,10 @@ int win_close(win_T *win, bool free_buf) if (!win->w_floating) { wp = frame2win(win_altframe(win, NULL)); } else { - if (win_valid(prevwin)) { + if (win_valid(prevwin) && prevwin != win) { wp = prevwin; } else { - wp = curtab->tp_firstwin; + wp = firstwin; } } @@ -2448,7 +2477,7 @@ int win_close(win_T *win, bool free_buf) } if (!was_floating) { - if (p_ea && (*p_ead == 'b' || *p_ead == dir)) { + if (!curwin->w_floating && p_ea && (*p_ead == 'b' || *p_ead == dir)) { // If the frame of the closed window contains the new current window, // only resize that frame. Otherwise resize all windows. win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir); @@ -2561,15 +2590,12 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) free_tabpage(tp); } -/* - * Free the memory used for a window. - * Returns a pointer to the window that got the freed up space. - */ -static win_T * -win_free_mem ( +// Free the memory used for a window. +// Returns a pointer to the window that got the freed up space. +static win_T *win_free_mem( win_T *win, - int *dirp, /* set to 'v' or 'h' for direction if 'ea' */ - tabpage_T *tp /* tab page "win" is in, NULL for current */ + int *dirp, // set to 'v' or 'h' for direction if 'ea' + tabpage_T *tp // tab page "win" is in, NULL for current ) { frame_T *frp; @@ -2581,18 +2607,20 @@ win_free_mem ( wp = winframe_remove(win, dirp, tp); xfree(frp); } else { - if (win_valid(prevwin)) { + *dirp = 'h'; // Dummy value. + if (win_valid(prevwin) && prevwin != win) { wp = prevwin; } else { - wp = curtab->tp_firstwin; + wp = firstwin; } } win_free(win, tp); - /* When deleting the current window of another tab page select a new - * current window. */ - if (tp != NULL && win == tp->tp_curwin) + // When deleting the current window of another tab page select a new + // current window. + if (tp != NULL && win == tp->tp_curwin) { tp->tp_curwin = wp; + } return wp; } @@ -2665,9 +2693,9 @@ winframe_remove ( frp3 = frp_close->fr_next; while (frp != NULL || frp3 != NULL) { if (frp != NULL) { - if (frp->fr_win != NULL && !frp->fr_win->w_p_wfh) { + if (!frame_fixed_height(frp)) { frp2 = frp; - wp = frp->fr_win; + wp = frame2win(frp2); break; } frp = frp->fr_prev; @@ -2694,9 +2722,9 @@ winframe_remove ( frp3 = frp_close->fr_next; while (frp != NULL || frp3 != NULL) { if (frp != NULL) { - if (frp->fr_win != NULL && !frp->fr_win->w_p_wfw) { + if (!frame_fixed_width(frp)) { frp2 = frp; - wp = frp->fr_win; + wp = frame2win(frp2); break; } frp = frp->fr_prev; @@ -3384,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); } /* @@ -3764,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 )) @@ -3806,7 +3839,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab) for (win_T *wp = firstwin; wp; wp = wp->w_next) { if (wp->w_floating && !wp->w_float_config.external) { - win_config_float(wp, wp->w_width, wp->w_height, wp->w_float_config); + win_config_float(wp, wp->w_float_config); } wp->w_pos_changed = true; } @@ -3818,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; @@ -4006,24 +4039,25 @@ tabpage_T *win_find_tabpage(win_T *win) return NULL; } -/* - * Move to window above or below "count" times. - */ -static void -win_goto_ver ( - int up, /* TRUE to go to win above */ - long count -) +/// Get the above or below neighbor window of the specified window. +/// +/// Returns the specified window if the neighbor is not found. +/// Returns the previous window if the specifiecied window is a floating window. +/// +/// @param up true for the above neighbor +/// @param count nth neighbor window +/// +/// @return found window +win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count) { frame_T *fr; frame_T *nfr; frame_T *foundfr; - foundfr = curwin->w_frame; + foundfr = wp->w_frame; - if (curwin->w_floating) { - win_goto(prevwin); - return; + if (wp->w_floating) { + return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin; } while (count--) { @@ -4033,14 +4067,17 @@ win_goto_ver ( */ fr = foundfr; for (;; ) { - if (fr == topframe) + if (fr == tp->tp_topframe) { goto end; - if (up) + } + if (up) { nfr = fr->fr_prev; - else + } else { nfr = fr->fr_next; - if (fr->fr_parent->fr_layout == FR_COL && nfr != NULL) + } + if (fr->fr_parent->fr_layout == FR_COL && nfr != NULL) { break; + } fr = fr->fr_parent; } @@ -4054,11 +4091,12 @@ win_goto_ver ( } fr = nfr->fr_child; if (nfr->fr_layout == FR_ROW) { - /* Find the frame at the cursor row. */ + // Find the frame at the cursor row. while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width - <= curwin->w_wincol + curwin->w_wcol) + <= wp->w_wincol + wp->w_wcol) { fr = fr->fr_next; + } } if (nfr->fr_layout == FR_COL && up) while (fr->fr_next != NULL) @@ -4067,28 +4105,40 @@ win_goto_ver ( } } end: - if (foundfr != NULL) - win_goto(foundfr->fr_win); + return foundfr != NULL ? foundfr->fr_win : NULL; } -/* - * Move to left or right window. - */ -static void -win_goto_hor ( - int left, /* TRUE to go to left win */ - long count -) +/// Move to window above or below "count" times. +/// +/// @param up true to go to win above +/// @param count go count times into direction +static void win_goto_ver(bool up, long count) +{ + win_T *win = win_vert_neighbor(curtab, curwin, up, count); + if (win != NULL) { + win_goto(win); + } +} + +/// Get the left or right neighbor window of the specified window. +/// +/// Returns the specified window if the neighbor is not found. +/// Returns the previous window if the specifiecied window is a floating window. +/// +/// @param left true for the left neighbor +/// @param count nth neighbor window +/// +/// @return found window +win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count) { frame_T *fr; frame_T *nfr; frame_T *foundfr; - foundfr = curwin->w_frame; + foundfr = wp->w_frame; - if (curwin->w_floating) { - win_goto(prevwin); - return; + if (wp->w_floating) { + return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin; } while (count--) { @@ -4098,14 +4148,17 @@ win_goto_hor ( */ fr = foundfr; for (;; ) { - if (fr == topframe) + if (fr == tp->tp_topframe) { goto end; - if (left) + } + if (left) { nfr = fr->fr_prev; - else + } else { nfr = fr->fr_next; - if (fr->fr_parent->fr_layout == FR_ROW && nfr != NULL) + } + if (fr->fr_parent->fr_layout == FR_ROW && nfr != NULL) { break; + } fr = fr->fr_parent; } @@ -4122,7 +4175,7 @@ win_goto_hor ( /* Find the frame at the cursor row. */ while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height - <= curwin->w_winrow + curwin->w_wrow) + <= wp->w_winrow + wp->w_wrow) fr = fr->fr_next; } if (nfr->fr_layout == FR_ROW && left) @@ -4132,8 +4185,19 @@ win_goto_hor ( } } end: - if (foundfr != NULL) - win_goto(foundfr->fr_win); + return foundfr != NULL ? foundfr->fr_win : NULL; +} + +/// Move to left or right window. +/// +/// @param left true to go to left window +/// @param count go count times into direction +static void win_goto_hor(bool left, long count) +{ + win_T *win = win_horz_neighbor(curtab, curwin, left, count); + if (win != NULL) { + win_goto(win); + } } /* @@ -4242,9 +4306,12 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid, apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); } if (trigger_enter_autocmds) { - apply_autocmds(EVENT_WINENTER, NULL, NULL, FALSE, curbuf); - if (other_buffer) - apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); + if (other_buffer) { + apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); + } + apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); + curwin->w_last_cursormoved = curwin->w_cursor; } maketitle(); @@ -4374,6 +4441,7 @@ static win_T *win_alloc(win_T *after, int hidden) new_wp->w_cursor.lnum = 1; new_wp->w_scbind_pos = 1; new_wp->w_floating = 0; + new_wp->w_float_config = FLOAT_CONFIG_INIT; /* We won't calculate w_fraction until resizing the window */ new_wp->w_fraction = 0; @@ -4642,8 +4710,12 @@ void win_size_restore(garray_T *gap) { int i = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - frame_setwidth(wp->w_frame, ((int *)gap->ga_data)[i++]); - win_setheight_win(((int *)gap->ga_data)[i++], wp); + int width = ((int *)gap->ga_data)[i++]; + int height = ((int *)gap->ga_data)[i++]; + if (!wp->w_floating) { + frame_setwidth(wp->w_frame, width); + win_setheight_win(height, wp); + } } } /* recompute the window positions */ @@ -4666,7 +4738,7 @@ int win_comp_pos(void) // Too often, but when we support anchoring floats to split windows, // this will be needed for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) { - win_config_float(wp, wp->w_width, wp->w_height, wp->w_float_config); + win_config_float(wp, wp->w_float_config); } return row; @@ -4739,7 +4811,8 @@ void win_setheight_win(int height, win_T *win) if (win->w_floating) { if (win->w_float_config.external) { - win_config_float(win, win->w_width, height, win->w_float_config); + win->w_float_config.height = height; + win_config_float(win, win->w_float_config); } else { beep_flush(); return; @@ -4944,7 +5017,8 @@ void win_setwidth_win(int width, win_T *wp) } if (wp->w_floating) { if (wp->w_float_config.external) { - win_config_float(wp, width, wp->w_height, wp->w_float_config); + wp->w_float_config.width = width; + win_config_float(wp, wp->w_float_config); } else { beep_flush(); return; @@ -5770,7 +5844,7 @@ last_status ( { /* Don't make a difference between horizontal or vertical split. */ last_status_rec(topframe, (p_ls == 2 - || (p_ls == 1 && (morewin || !ONE_WINDOW)))); + || (p_ls == 1 && (morewin || !one_window())))); } static void last_status_rec(frame_T *fr, int statusline) @@ -5889,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 @@ -6044,7 +6142,7 @@ static win_T *get_snapshot_focus(int idx) } } - return sn->fr_win; + return win_valid(sn->fr_win) ? sn->fr_win : NULL; } /* |