diff options
Diffstat (limited to 'src')
74 files changed, 6545 insertions, 1991 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 3e1209d1b1..a5f8b0974e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -23,7 +23,10 @@ #include "nvim/memory.h" #include "nvim/misc1.h" #include "nvim/ex_cmds.h" +#include "nvim/map_defs.h" +#include "nvim/map.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/fileio.h" #include "nvim/move.h" #include "nvim/syntax.h" @@ -101,25 +104,47 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) return rv; } -/// Activates buffer-update events on a channel, or as lua callbacks. +/// Activates buffer-update events on a channel, or as Lua callbacks. +/// +/// Example (Lua): capture buffer updates in a global `events` variable +/// (use "print(vim.inspect(events))" to see its contents): +/// <pre> +/// events = {} +/// vim.api.nvim_buf_attach(0, false, { +/// on_lines=function(...) table.insert(events, {...}) end}) +/// </pre> +/// +/// @see |nvim_buf_detach()| +/// @see |api-buffer-updates-lua| /// /// @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`. Not used for lua callbacks. +/// @param send_buffer True if the initial notification should contain the +/// whole buffer: first notification will be `nvim_buf_lines_event`. +/// Else the first notification will be `nvim_buf_changedtick_event`. +/// Not for Lua callbacks. /// @param opts Optional parameters. -/// - `on_lines`: lua callback received on change. -/// - `on_changedtick`: lua callback received on changedtick -/// increment without text change. -/// - `utf_sizes`: include UTF-32 and UTF-16 size of -/// the replaced region. -/// See |api-buffer-updates-lua| for more information +/// - on_lines: Lua callback invoked on change. +/// Return `true` to detach. Args: +/// - buffer handle +/// - b:changedtick +/// - first line that changed (zero-indexed) +/// - last line that was changed +/// - last line in the updated range +/// - byte count of previous contents +/// - deleted_codepoints (if `utf_sizes` is true) +/// - deleted_codeunits (if `utf_sizes` is true) +/// - on_changedtick: Lua callback invoked on changedtick +/// increment without text change. Args: +/// - buffer handle +/// - b:changedtick +/// - on_detach: Lua callback invoked on detach. Args: +/// - buffer handle +/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced +/// region, as args to `on_lines`. /// @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. -/// TODO: LUA_API_NO_EVAL +/// @return False if attach failed (invalid parameter, or buffer isn't loaded); +/// otherwise True. TODO: LUA_API_NO_EVAL Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer, @@ -183,13 +208,14 @@ error: /// Deactivates buffer-update events on the channel. /// -/// For Lua callbacks see |api-lua-detach|. +/// @see |nvim_buf_attach()| +/// @see |api-lua-detach| for detaching Lua callbacks /// /// @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. +/// @return False if detach failed (because the buffer isn't loaded); +/// otherwise True. Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err) @@ -529,7 +555,8 @@ void nvim_buf_set_lines(uint64_t channel_id, (linenr_T)(end - 1), MAXLNUM, (long)extra, - false); + false, + kExtmarkUndo); changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); @@ -984,6 +1011,238 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) return rv; } +/// Returns position for a given extmark id +/// +/// @param buffer The buffer handle +/// @param namespace a identifier returned previously with nvim_create_namespace +/// @param id the extmark id +/// @param[out] err Details of an error that may have occurred +/// @return (row, col) tuple or empty list () if extmark id was absent +ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, + Integer id, Error *err) + FUNC_API_SINCE(7) +{ + Array rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return rv; + } + + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + return rv; + } + + Extmark *extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + if (!extmark) { + return rv; + } + ADD(rv, INTEGER_OBJ((Integer)extmark->line->lnum-1)); + ADD(rv, INTEGER_OBJ((Integer)extmark->col-1)); + return rv; +} + +/// List extmarks in a range (inclusive) +/// +/// range ends can be specified as (row, col) tuples, as well as extmark +/// ids in the same namespace. In addition, 0 and -1 works as shorthands +/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be +/// queried as: +/// +/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// +/// If end is a lower position than start, then the range will be traversed +/// backwards. This is mostly useful with limited amount, to be able to get the +/// first marks prior to a given position. +/// +/// @param buffer The buffer handle +/// @param ns_id An id returned previously from nvim_create_namespace +/// @param start One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param end One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param opts additional options. Supports the keys: +/// - amount: Maximum number of marks to return +/// @param[out] err Details of an error that may have occurred +/// @return [[extmark_id, row, col], ...] +Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, + Object start, Object end, Dictionary opts, + Error *err) + FUNC_API_SINCE(7) +{ + Array rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return rv; + } + + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + return rv; + } + Integer amount = -1; + + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object *v = &opts.items[i].value; + if (strequal("amount", k.data)) { + if (v->type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, "amount is not an integer"); + return rv; + } + amount = v->data.integer; + v->data.integer = LUA_NOREF; + } else { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return rv; + } + } + + if (amount == 0) { + return rv; + } + + + bool reverse = false; + + linenr_T l_lnum; + colnr_T l_col; + if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) { + return rv; + } + + linenr_T u_lnum; + colnr_T u_col; + if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) { + return rv; + } + + if (l_lnum > u_lnum || (l_lnum == u_lnum && l_col > u_col)) { + reverse = true; + linenr_T tmp_lnum = l_lnum; + l_lnum = u_lnum; + u_lnum = tmp_lnum; + colnr_T tmp_col = l_col; + l_col = u_col; + u_col = tmp_col; + } + + + ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, + u_lnum, u_col, (int64_t)amount, + reverse); + + for (size_t i = 0; i < kv_size(marks); i++) { + Array mark = ARRAY_DICT_INIT; + Extmark *extmark = kv_A(marks, i); + ADD(mark, INTEGER_OBJ((Integer)extmark->mark_id)); + ADD(mark, INTEGER_OBJ(extmark->line->lnum-1)); + ADD(mark, INTEGER_OBJ(extmark->col-1)); + ADD(rv, ARRAY_OBJ(mark)); + } + + kv_destroy(marks); + return rv; +} + +/// Create or update an extmark at a position +/// +/// If an invalid namespace is given, an error will be raised. +/// +/// To create a new extmark, pass in id=0. The new extmark id will be +/// returned. To move an existing mark, pass in its id. +/// +/// It is also allowed to create a new mark by passing in a previously unused +/// id, but the caller must then keep track of existing and unused ids itself. +/// This is mainly useful over RPC, to avoid needing to wait for the return +/// value. +/// +/// @param buffer The buffer handle +/// @param ns_id a identifier returned previously with nvim_create_namespace +/// @param id The extmark's id or 0 to create a new mark. +/// @param line The row to set the extmark to. +/// @param col The column to set the extmark to. +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Details of an error that may have occurred +/// @return the id of the extmark. +Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id, + Integer line, Integer col, + Dictionary opts, Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return 0; + } + + size_t len = 0; + if (line < 0 || line > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, "line value outside range"); + return 0; + } else if (line < buf->b_ml.ml_line_count) { + len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false)); + } + + if (col == -1) { + col = (Integer)len; + } else if (col < -1 || col > (Integer)len) { + api_set_error(err, kErrorTypeValidation, "col value outside range"); + return 0; + } + + uint64_t id_num; + if (id == 0) { + id_num = extmark_free_id_get(buf, (uint64_t)ns_id); + } else if (id > 0) { + id_num = (uint64_t)id; + } else { + api_set_error(err, kErrorTypeValidation, _("Invalid mark id")); + return 0; + } + + extmark_set(buf, (uint64_t)ns_id, id_num, + (linenr_T)line+1, (colnr_T)col+1, kExtmarkUndo); + + return (Integer)id_num; +} + +/// Remove an extmark +/// +/// @param buffer The buffer handle +/// @param ns_id a identifier returned previously with nvim_create_namespace +/// @param id The extmarks's id +/// @param[out] err Details of an error that may have occurred +/// @return true on success, false if the extmark was not found. +Boolean nvim_buf_del_extmark(Buffer buffer, + Integer ns_id, + Integer id, + Error *err) + FUNC_API_SINCE(7) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return false; + } + if (!ns_initialized((uint64_t)ns_id)) { + api_set_error(err, kErrorTypeValidation, _("Invalid ns_id")); + return false; + } + + return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id, kExtmarkUndo); +} + /// Adds a highlight to buffer. /// /// Useful for plugins that dynamically generate highlights to a buffer @@ -1082,6 +1341,10 @@ void nvim_buf_clear_namespace(Buffer buffer, } bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end); + extmark_clear(buf, ns_id == -1 ? 0 : (uint64_t)ns_id, + (linenr_T)line_start+1, + (linenr_T)line_end, + kExtmarkUndo); } /// Clears highlights and virtual text from namespace and range of lines @@ -1192,6 +1455,56 @@ free_exit: return 0; } +/// Get the virtual text (annotation) for a buffer line. +/// +/// The virtual text is returned as list of lists, whereas the inner lists have +/// either one or two elements. The first element is the actual text, the +/// optional second element is the highlight group. +/// +/// The format is exactly the same as given to nvim_buf_set_virtual_text(). +/// +/// If there is no virtual text associated with the given line, an empty list +/// is returned. +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param line Line to get the virtual text from (zero-indexed) +/// @param[out] err Error details, if any +/// @return List of virtual text chunks +Array nvim_buf_get_virtual_text(Buffer buffer, Integer lnum, Error *err) + FUNC_API_SINCE(7) +{ + Array chunks = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return chunks; + } + + if (lnum < 0 || lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line number outside range"); + return chunks; + } + + BufhlLine *lineinfo = bufhl_tree_ref(&buf->b_bufhl_info, (linenr_T)(lnum + 1), + false); + if (!lineinfo) { + return chunks; + } + + for (size_t i = 0; i < lineinfo->virt_text.size; i++) { + Array chunk = ARRAY_DICT_INIT; + VirtTextChunk *vtc = &lineinfo->virt_text.items[i]; + ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); + if (vtc->hl_id > 0) { + ADD(chunk, STRING_OBJ(cstr_to_string( + (const char *)syn_id2name(vtc->hl_id)))); + } + ADD(chunks, ARRAY_OBJ(chunk)); + } + + return chunks; +} + Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 2056cb07e3..fbfdb27827 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -10,6 +10,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/handle.h" +#include "nvim/api/vim.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/lua/executor.h" #include "nvim/ascii.h" @@ -23,6 +24,7 @@ #include "nvim/eval/typval.h" #include "nvim/map_defs.h" #include "nvim/map.h" +#include "nvim/mark_extended.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/version.h" @@ -1505,3 +1507,127 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) return mappings; } + +// Returns an extmark given an id or a positional index +// If throw == true then an error will be raised if nothing +// was found +// Returns NULL if something went wrong +Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id, + Error *err, bool throw) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return NULL; + } + + Extmark *extmark = NULL; + if (id.type == kObjectTypeArray) { + if (id.data.array.size != 2) { + api_set_error(err, kErrorTypeValidation, + _("Position must have 2 elements")); + return NULL; + } + linenr_T row = (linenr_T)id.data.array.items[0].data.integer; + colnr_T col = (colnr_T)id.data.array.items[1].data.integer; + if (row < 1 || col < 1) { + if (throw) { + api_set_error(err, kErrorTypeValidation, _("Row and column MUST be > 0")); + } + return NULL; + } + extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col); + } else if (id.type != kObjectTypeInteger) { + if (throw) { + api_set_error(err, kErrorTypeValidation, + _("Mark id must be an int or [row, col]")); + } + return NULL; + } else if (id.data.integer < 0) { + if (throw) { + api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); + } + return NULL; + } else { + extmark = extmark_from_id(buf, + (uint64_t)namespace, + (uint64_t)id.data.integer); + } + + if (!extmark) { + if (throw) { + api_set_error(err, kErrorTypeValidation, _("Mark doesn't exist")); + } + return NULL; + } + return extmark; +} + +// Is the Namespace in use? +bool ns_initialized(uint64_t ns) +{ + if (ns < 1) { + return false; + } + return ns < (uint64_t)next_namespace_id; +} + +/// Get line and column from extmark object +/// +/// Extmarks may be queried from position or name or even special names +/// in the future such as "cursor". This function sets the line and col +/// to make the extmark functions recognize what's required +/// +/// @param[out] lnum lnum to be set +/// @param[out] colnr col to be set +bool set_extmark_index_from_obj(buf_T *buf, Integer namespace, + Object obj, linenr_T *lnum, colnr_T *colnr, + Error *err) +{ + // Check if it is mark id + if (obj.type == kObjectTypeInteger) { + Integer id = obj.data.integer; + if (id == 0) { + *lnum = 1; + *colnr = 1; + return true; + } else if (id == -1) { + *lnum = MAXLNUM; + *colnr = MAXCOL; + return true; + } else if (id < 0) { + api_set_error(err, kErrorTypeValidation, _("Mark id must be positive")); + return false; + } + + Extmark *extmark = extmark_from_id(buf, (uint64_t)namespace, (uint64_t)id); + if (extmark) { + *lnum = extmark->line->lnum; + *colnr = extmark->col; + return true; + } else { + api_set_error(err, kErrorTypeValidation, _("No mark with requested id")); + return false; + } + + // Check if it is a position + } else if (obj.type == kObjectTypeArray) { + Array pos = obj.data.array; + if (pos.size != 2 + || pos.items[0].type != kObjectTypeInteger + || pos.items[1].type != kObjectTypeInteger) { + api_set_error(err, kErrorTypeValidation, + _("Position must have 2 integer elements")); + return false; + } + Integer line = pos.items[0].data.integer; + Integer col = pos.items[1].data.integer; + *lnum = (linenr_T)(line >= 0 ? line + 1 : MAXLNUM); + *colnr = (colnr_T)(col >= 0 ? col + 1 : MAXCOL); + return true; + } else { + api_set_error(err, kErrorTypeValidation, + _("Position must be a mark id Integer or position Array")); + return false; + } +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 0ea7667428..8930f252f6 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -102,6 +102,20 @@ typedef struct { int did_emsg; } TryState; +// `msg_list` controls the collection of abort-causing non-exception errors, +// which would otherwise be ignored. This pattern is from do_cmdline(). +// +// TODO(bfredl): prepare error-handling at "top level" (nv_event). +#define TRY_WRAP(code) \ + do { \ + struct msglist **saved_msg_list = msg_list; \ + struct msglist *private_msg_list; \ + msg_list = &private_msg_list; \ + private_msg_list = NULL; \ + code \ + msg_list = saved_msg_list; /* Restore the exception context. */ \ + } while (0) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" #endif diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 602733fd31..10f7dd1a7b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -39,6 +39,7 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/state.h" +#include "nvim/mark_extended.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" @@ -53,20 +54,6 @@ # include "api/vim.c.generated.h" #endif -// `msg_list` controls the collection of abort-causing non-exception errors, -// which would otherwise be ignored. This pattern is from do_cmdline(). -// -// TODO(bfredl): prepare error-handling at "top level" (nv_event). -#define TRY_WRAP(code) \ - do { \ - struct msglist **saved_msg_list = msg_list; \ - struct msglist *private_msg_list; \ - msg_list = &private_msg_list; \ - private_msg_list = NULL; \ - code \ - msg_list = saved_msg_list; /* Restore the exception context. */ \ - } while (0) - void api_vim_init(void) FUNC_API_NOEXPORT { @@ -1054,10 +1041,9 @@ fail: /// @param enter Enter the window (make it the current window) /// @param config Map defining the window configuration. Keys: /// - `relative`: Sets the window layout to "floating", placed at (row,col) -/// coordinates relative to one of: +/// coordinates relative to: /// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window by -/// default. +/// - "win" Window given by the `win` field, or current window. /// - "cursor" Cursor position in current window. /// - `win`: |window-ID| for relative="win". /// - `anchor`: Decides which corner of the float to place at (row,col): @@ -1266,7 +1252,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) draining = true; goto theend; } - if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) { + if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) { ResetRedobuff(); AppendCharToRedobuff('a'); // Dot-repeat. } @@ -1284,7 +1270,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) } } } - if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) { + if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) { AppendCharToRedobuff(ESC); // Dot-repeat. } theend: diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index c223679596..a52789c795 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -21,9 +21,9 @@ return { 'BufWritePre', -- before writing a buffer 'ChanInfo', -- info was received about channel 'ChanOpen', -- channel was opened - 'CmdLineChanged', -- command line was modified - 'CmdLineEnter', -- after entering cmdline mode - 'CmdLineLeave', -- before leaving cmdline mode + 'CmdlineChanged', -- command line was modified + 'CmdlineEnter', -- after entering cmdline mode + 'CmdlineLeave', -- before leaving cmdline mode 'CmdUndefined', -- command undefined 'CmdWinEnter', -- after entering the cmdline window 'CmdWinLeave', -- before leaving the cmdline window diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 1d5aa8ba9b..79f339b3aa 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -53,6 +53,7 @@ #include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -816,6 +817,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) } uc_clear(&buf->b_ucmds); // clear local user commands buf_delete_signs(buf, (char_u *)"*"); // delete any signs + extmark_free_all(buf); // delete any extmarks bufhl_clear_all(buf); // delete any highligts map_clear_int(buf, MAP_ALL_MODES, true, false); // clear local mappings map_clear_int(buf, MAP_ALL_MODES, true, true); // clear local abbrevs @@ -5496,6 +5498,7 @@ void bufhl_clear_line_range(buf_T *buf, linenr_T line_start, linenr_T line_end) { + // TODO(bfredl): implement kb_itr_interval to jump directly to the first line kbitr_t(bufhl) itr; BufhlLine *l, t = BUFHLLINE_INIT(line_start); if (!kb_itr_get(bufhl, &buf->b_bufhl_info, &t, &itr)) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ca740dea21..700d8b82e6 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -115,6 +115,9 @@ typedef uint16_t disptick_T; // display tick type #include "nvim/os/fs_defs.h" // for FileID #include "nvim/terminal.h" // for Terminal +#include "nvim/lib/kbtree.h" +#include "nvim/mark_extended.h" + /* * The taggy struct is used to store the information about a :tag command. */ @@ -805,6 +808,10 @@ struct file_buffer { kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights + PMap(uint64_t) *b_extmark_ns; // extmark namespaces + kbtree_t(extmarklines) b_extlines; // extmarks + kvec_t(ExtmarkLine *) b_extmark_move_space; // temp space for extmarks + // array of channel_id:s which have asked to receive updates for this // buffer. kvec_t(uint64_t) update_channels; @@ -911,19 +918,19 @@ typedef struct w_line { * or row (FR_ROW) layout or is a leaf, which has a window. */ struct frame_S { - char fr_layout; /* FR_LEAF, FR_COL or FR_ROW */ + char fr_layout; // FR_LEAF, FR_COL or FR_ROW int fr_width; - int fr_newwidth; /* new width used in win_equal_rec() */ + int fr_newwidth; // new width used in win_equal_rec() int fr_height; - int fr_newheight; /* new height used in win_equal_rec() */ - frame_T *fr_parent; /* containing frame or NULL */ - frame_T *fr_next; /* frame right or below in same parent, NULL - for first */ - frame_T *fr_prev; /* frame left or above in same parent, NULL - for last */ - /* fr_child and fr_win are mutually exclusive */ - frame_T *fr_child; /* first contained frame */ - win_T *fr_win; /* window that fills this frame */ + int fr_newheight; // new height used in win_equal_rec() + frame_T *fr_parent; // containing frame or NULL + frame_T *fr_next; // frame right or below in same parent, NULL + // for last + frame_T *fr_prev; // frame left or above in same parent, NULL + // for first + // fr_child and fr_win are mutually exclusive + frame_T *fr_child; // first contained frame + win_T *fr_win; // window that fills this frame }; #define FR_LEAF 0 /* frame is a leaf */ diff --git a/src/nvim/change.c b/src/nvim/change.c index ba80e71ae6..7558055696 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -17,6 +17,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/memline.h" #include "nvim/misc1.h" #include "nvim/move.h" @@ -372,7 +373,7 @@ void appended_lines_mark(linenr_T lnum, long count) // Skip mark_adjust when adding a line after the last one, there can't // be marks there. But it's still needed in diff mode. if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false, kExtmarkUndo); } changed_lines(lnum + 1, 0, lnum + 1, count, true); } @@ -390,7 +391,8 @@ void deleted_lines(linenr_T lnum, long count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false); + mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false, + kExtmarkUndo); changed_lines(lnum, 0, lnum + count, -count, true); } @@ -951,6 +953,9 @@ int open_line( bool did_append; // appended a new line int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting + linenr_T lnum = curwin->w_cursor.lnum; + colnr_T mincol = curwin->w_cursor.col + 1; + // make a copy of the current line so we can mess with it char_u *saved_line = vim_strsave(get_cursor_line_ptr()); @@ -1574,7 +1579,8 @@ int open_line( // be marks there. But still needed in diff mode. if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false, + kExtmarkUndo); } did_append = true; } else { @@ -1663,8 +1669,12 @@ int open_line( if (flags & OPENLINE_MARKFIX) { mark_col_adjust(curwin->w_cursor.lnum, curwin->w_cursor.col + less_cols_off, - 1L, (long)-less_cols, 0); + 1L, (long)-less_cols, 0, kExtmarkNOOP); } + // Always move extmarks - Here we move only the line where the + // cursor is, the previous mark_adjust takes care of the lines after + extmark_col_adjust(curbuf, lnum, mincol, 1L, (long)-less_cols, + kExtmarkUndo); } else { changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); } diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 31552929dc..dccde01d29 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -44,7 +44,7 @@ #include "nvim/os/shell.h" static int diff_busy = false; // using diff structs, don't change them -static int diff_need_update = false; // ex_diffupdate needs to be called +static bool diff_need_update = false; // ex_diffupdate needs to be called // Flags obtained from the 'diffopt' option #define DIFF_FILLER 0x001 // display filler lines @@ -57,8 +57,9 @@ static int diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_VERTICAL 0x080 // vertical splits #define DIFF_HIDDEN_OFF 0x100 // diffoff when hidden #define DIFF_INTERNAL 0x200 // use internal xdiff algorithm +#define DIFF_CLOSE_OFF 0x400 // diffoff when closing window #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) -static int diff_flags = DIFF_INTERNAL | DIFF_FILLER; +static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; static long diff_algorithm = 0; @@ -490,7 +491,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, } if (tp == curtab) { - diff_redraw(true); + // Don't redraw right away, this updates the diffs, which can be slow. + need_diff_redraw = true; // Need to recompute the scroll binding, may remove or add filler // lines (e.g., when adding lines above w_topline). But it's slow when @@ -634,8 +636,9 @@ static int diff_check_sanity(tabpage_T *tp, diff_T *dp) /// Mark all diff buffers in the current tab page for redraw. /// /// @param dofold Also recompute the folds -static void diff_redraw(int dofold) +void diff_redraw(bool dofold) { + need_diff_redraw = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (!wp->w_p_diff) { continue; @@ -1472,6 +1475,13 @@ void ex_diffoff(exarg_T *eap) diff_buf_clear(); } + if (!diffwin) { + diff_need_update = false; + curtab->tp_diff_invalid = false; + curtab->tp_diff_update = false; + diff_clear(curtab); + } + // Remove "hor" from from 'scrollopt' if there are no diff windows left. if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) { do_cmdline_cmd("set sbo-=hor"); @@ -1712,6 +1722,7 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, /// /// @param tp void diff_clear(tabpage_T *tp) + FUNC_ATTR_NONNULL_ALL { diff_T *p; diff_T *next_p; @@ -2141,6 +2152,9 @@ int diffopt_changed(void) } else if (STRNCMP(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; + } else if (STRNCMP(p, "closeoff", 8) == 0) { + p += 8; + diff_flags_new |= DIFF_CLOSE_OFF; } else if (STRNCMP(p, "indent-heuristic", 16) == 0) { p += 16; diff_indent_heuristic = XDF_INDENT_HEURISTIC; @@ -2216,6 +2230,13 @@ bool diffopt_hiddenoff(void) return (diff_flags & DIFF_HIDDEN_OFF) != 0; } +// Return true if 'diffopt' contains "closeoff". +bool diffopt_closeoff(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (diff_flags & DIFF_CLOSE_OFF) != 0; +} + /// Find the difference within a changed line. /// /// @param wp window whose current buffer to check @@ -2690,7 +2711,8 @@ void ex_diffgetput(exarg_T *eap) // Adjust marks. This will change the following entries! if (added != 0) { - mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false); + mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added, false, + kExtmarkUndo); if (curwin->w_cursor.lnum >= lnum) { // Adjust the cursor position if it's in/after the changed // lines. diff --git a/src/nvim/diff.h b/src/nvim/diff.h index 3624ce29bb..99a60381bd 100644 --- a/src/nvim/diff.h +++ b/src/nvim/diff.h @@ -4,6 +4,13 @@ #include "nvim/pos.h" #include "nvim/ex_cmds_defs.h" +// Value set from 'diffopt'. +EXTERN int diff_context INIT(= 6); // context for folds +EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode +EXTERN bool diff_need_scrollbind INIT(= false); + +EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw() + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "diff.h.generated.h" #endif diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 62e4f77e6e..cd0f3f4b9d 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -28,6 +28,7 @@ #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -1837,6 +1838,13 @@ change_indent ( xfree(new_line); } + + // change_indent seems to bec called twice, this combination only triggers + // once for both calls + if (new_cursor_col - vcol != 0) { + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, 0, 0, amount, + kExtmarkUndo); + } } /* @@ -3048,7 +3056,9 @@ static void ins_compl_clear(void) XFREE_CLEAR(compl_orig_text); compl_enter_selects = false; // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } /// Check that Insert completion is active. @@ -4118,7 +4128,7 @@ static int ins_compl_get_exp(pos_T *ini) compl_direction, compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, (linenr_T)0, NULL, NULL); + RE_LAST, NULL); } msg_silent--; if (!compl_started || set_match_pos) { @@ -4305,7 +4315,9 @@ static void ins_compl_delete(void) // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } // Insert the new text being completed. @@ -4327,6 +4339,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); + dict->dv_lock = VAR_FIXED; tv_dict_add_str( dict, S_LEN("word"), (const char *)EMPTY_IF_NULL(match->cp_str)); @@ -5587,6 +5600,9 @@ insertchar ( do_digraph(buf[i-1]); /* may be the start of a digraph */ buf[i] = NUL; ins_str(buf); + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + (colnr_T)(curwin->w_cursor.col + 1), 0, + (long)STRLEN(buf), kExtmarkUndo); if (flags & INSCHAR_CTRLV) { redo_literal(*buf); i = 1; @@ -5597,6 +5613,9 @@ insertchar ( } else { int cc; + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + (colnr_T)(curwin->w_cursor.col + 1), 0, + 1, kExtmarkUndo); if ((cc = utf_char2len(c)) > 1) { char_u buf[MB_MAXBYTES + 1]; @@ -5606,10 +5625,11 @@ insertchar ( AppendCharToRedobuff(c); } else { ins_char(c); - if (flags & INSCHAR_CTRLV) + if (flags & INSCHAR_CTRLV) { redo_literal(c); - else + } else { AppendCharToRedobuff(c); + } } } } @@ -6891,8 +6911,9 @@ static void mb_replace_pop_ins(int cc) for (i = 1; i < n; ++i) buf[i] = replace_pop(); ins_bytes_len(buf, n); - } else + } else { ins_char(cc); + } if (enc_utf8) /* Handle composing chars. */ @@ -8002,9 +8023,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) Insstart_orig.col = curwin->w_cursor.col; } - if (State & VREPLACE_FLAG) + if (State & VREPLACE_FLAG) { ins_char(' '); - else { + } else { ins_str((char_u *)" "); if ((State & REPLACE_FLAG)) replace_push(NUL); @@ -8482,8 +8503,17 @@ static bool ins_tab(void) } else { // otherwise use "tabstop" temp = (int)curbuf->b_p_ts; } + temp -= get_nolist_virtcol() % temp; + // Move extmarks + extmark_col_adjust(curbuf, + curwin->w_cursor.lnum, + curwin->w_cursor.col, + 0, + temp, + kExtmarkUndo); + /* * Insert the first space with ins_char(). It will delete one char in * replace mode. Insert the rest with ins_str(); it will not delete any @@ -8491,12 +8521,13 @@ static bool ins_tab(void) */ ins_char(' '); while (--temp > 0) { - if (State & VREPLACE_FLAG) + if (State & VREPLACE_FLAG) { ins_char(' '); - else { + } else { ins_str((char_u *)" "); - if (State & REPLACE_FLAG) /* no char replaced */ + if (State & REPLACE_FLAG) { // no char replaced replace_push(NUL); + } } } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 71ffb26cc2..9fe92a92cc 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -422,6 +422,7 @@ static struct vimvar { VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), }; #undef VV @@ -433,11 +434,14 @@ static struct vimvar { #define vv_str vv_di.di_tv.vval.v_string #define vv_list vv_di.di_tv.vval.v_list #define vv_dict vv_di.di_tv.vval.v_dict +#define vv_partial vv_di.di_tv.vval.v_partial #define vv_tv vv_di.di_tv /// Variable used for v: static ScopeDictDictItem vimvars_var; +static partial_T *vvlua_partial; + /// v: hashtab #define vimvarht vimvardict.dv_hashtab @@ -639,6 +643,13 @@ void eval_init(void) set_vim_var_nr(VV_ECHOSPACE, sc_col - 1); + vimvars[VV_LUA].vv_type = VAR_PARTIAL; + vvlua_partial = xcalloc(1, sizeof(partial_T)); + vimvars[VV_LUA].vv_partial = vvlua_partial; + // this value shouldn't be printed, but if it is, do not crash + vvlua_partial->pt_name = xmallocz(0); + vvlua_partial->pt_refcount++; + set_reg_var(0); // default for v:register is not 0 but '"' } @@ -1313,12 +1324,25 @@ int call_vim_function( { int doesrange; int ret; + int len = (int)STRLEN(func); + partial_T *pt = NULL; + + if (len >= 6 && !memcmp(func, "v:lua.", 6)) { + func += 6; + len = check_luafunc_name((const char *)func, false); + if (len == 0) { + ret = FAIL; + goto fail; + } + pt = vvlua_partial; + } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL, + ret = call_func(func, len, rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &doesrange, true, NULL, NULL); + &doesrange, true, pt, NULL); +fail: if (ret == FAIL) { tv_clear(rettv); } @@ -2462,6 +2486,13 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } + if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv) + && len == -1 && rettv == NULL) { + tv_clear(&var1); + EMSG2(e_illvar, "v:['lua']"); + return NULL; + } + if (lp->ll_di == NULL) { // Can't add "v:" or "a:" variable. if (lp->ll_dict == &vimvardict @@ -4699,7 +4730,7 @@ eval_index( if (evaluate) { n1 = 0; - if (!empty1 && rettv->v_type != VAR_DICT) { + if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) { n1 = tv_get_number(&var1); tv_clear(&var1); } @@ -4823,7 +4854,7 @@ eval_index( if (len == -1) { tv_clear(&var1); } - if (item == NULL) { + if (item == NULL || tv_is_luafunc(&item->di_tv)) { return FAIL; } @@ -6334,7 +6365,7 @@ static char_u *deref_func_name(const char *name, int *lenp, */ static int get_func_tv( - char_u *name, // name of the function + const char_u *name, // name of the function int len, // length of "name" typval_T *rettv, char_u **arg, // argument, pointing to the '(' @@ -6590,7 +6621,15 @@ call_func( rettv->vval.v_number = 0; error = ERROR_UNKNOWN; - if (!builtin_function((const char *)rfname, -1)) { + if (partial == vvlua_partial) { + if (len > 0) { + error = ERROR_NONE; + executor_call_lua((const char *)funcname, len, + argvars, argcount, rettv); + } else { + error = ERROR_UNKNOWN; + } + } else if (!builtin_function((const char *)rfname, -1)) { // User defined function. if (partial != NULL && partial->pt_func != NULL) { fp = partial->pt_func; @@ -6707,14 +6746,14 @@ call_func( /// /// @param ermsg must be passed without translation (use N_() instead of _()). /// @param name function name -static void emsg_funcname(char *ermsg, char_u *name) +static void emsg_funcname(char *ermsg, const char_u *name) { char_u *p; if (*name == K_SPECIAL) { p = concat_str((char_u *)"<SNR>", name + 3); } else { - p = name; + p = (char_u *)name; } EMSG2(_(ermsg), p); @@ -8711,7 +8750,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (p == NULL) { rettv->v_type = VAR_SPECIAL; - rettv->vval.v_number = kSpecialVarNull; + rettv->vval.v_special = kSpecialVarNull; return; } rettv->vval.v_string = p; @@ -14617,6 +14656,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) long time_limit = 0; int options = SEARCH_KEEP; int subpatnum; + searchit_arg_T sia; const char *const pat = tv_get_string(&argvars[0]); dir = get_search_arg(&argvars[1], flagsp); // May set p_ws. @@ -14664,8 +14704,11 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) } pos = save_cursor = curwin->w_cursor; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = (linenr_T)lnum_stop; + sia.sa_tm = &tm; subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, - options, RE_SEARCH, (linenr_T)lnum_stop, &tm, NULL); + options, RE_SEARCH, &sia); if (subpatnum != FAIL) { if (flags & SP_SUBPAT) retval = subpatnum; @@ -15237,8 +15280,13 @@ do_searchpair( clearpos(&foundpos); pat = pat3; for (;; ) { + searchit_arg_T sia; + memset(&sia, 0, sizeof(sia)); + sia.sa_stop_lnum = lnum_stop; + sia.sa_tm = &tm; + n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L, - options, RE_SEARCH, lnum_stop, &tm, NULL); + options, RE_SEARCH, &sia); if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) { // didn't find it or found the first match again: FAIL break; @@ -15660,7 +15708,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *name = tv_get_string_buf(&argvars[0], namebuf); if (argvars[1].v_type == VAR_SPECIAL - && argvars[1].vval.v_number == kSpecialVarNull) { + && argvars[1].vval.v_special == kSpecialVarNull) { os_unsetenv(name); } else { os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); @@ -20159,6 +20207,26 @@ static void check_vars(const char *name, size_t len) } } +/// check if special v:lua value for calling lua functions +static bool tv_is_luafunc(typval_T *tv) +{ + return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial; +} + +/// check the function name after "v:lua." +static int check_luafunc_name(const char *str, bool paren) +{ + const char *p = str; + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + p++; + } + if (*p != (paren ? '(' : NUL)) { + return 0; + } else { + return (int)(p-str); + } +} + /// Handle expr[expr], expr[expr:expr] subscript and .name lookup. /// Also handle function call with Funcref variable: func(expr) /// Can all be combined: dict.func(expr)[idx]['func'](expr) @@ -20172,9 +20240,30 @@ handle_subscript( { int ret = OK; dict_T *selfdict = NULL; - char_u *s; + const char_u *s; int len; typval_T functv; + int slen = 0; + bool lua = false; + + if (tv_is_luafunc(rettv)) { + if (**arg != '.') { + tv_clear(rettv); + ret = FAIL; + } else { + (*arg)++; + + lua = true; + s = (char_u *)(*arg); + slen = check_luafunc_name(*arg, true); + if (slen == 0) { + tv_clear(rettv); + ret = FAIL; + } + (*arg) += slen; + } + } + while (ret == OK && (**arg == '[' @@ -20191,14 +20280,16 @@ handle_subscript( // Invoke the function. Recursive! if (functv.v_type == VAR_PARTIAL) { pt = functv.vval.v_partial; - s = partial_name(pt); + if (!lua) { + s = partial_name(pt); + } } else { s = functv.vval.v_string; } } else { s = (char_u *)""; } - ret = get_func_tv(s, (int)STRLEN(s), rettv, (char_u **)arg, + ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &len, evaluate, pt, selfdict); @@ -21733,22 +21824,31 @@ void ex_function(exarg_T *eap) } // Check for ":let v =<< [trim] EOF" + // and ":let [a, b] =<< [trim] EOF" arg = skipwhite(skiptowhite(p)); - arg = skipwhite(skiptowhite(arg)); - if (arg[0] == '=' && arg[1] == '<' && arg[2] =='<' - && ((p[0] == 'l' && p[1] == 'e' - && (!ASCII_ISALNUM(p[2]) - || (p[2] == 't' && !ASCII_ISALNUM(p[3])))))) { - p = skipwhite(arg + 3); - if (STRNCMP(p, "trim", 4) == 0) { - // Ignore leading white space. - p = skipwhite(p + 4); - heredoc_trimmed = vim_strnsave(theline, - (int)(skipwhite(theline) - theline)); + if (*arg == '[') { + arg = vim_strchr(arg, ']'); + } + if (arg != NULL) { + arg = skipwhite(skiptowhite(arg)); + if (arg[0] == '=' + && arg[1] == '<' + && arg[2] =='<' + && (p[0] == 'l' + && p[1] == 'e' + && (!ASCII_ISALNUM(p[2]) + || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { + p = skipwhite(arg + 3); + if (STRNCMP(p, "trim", 4) == 0) { + // Ignore leading white space. + p = skipwhite(p + 4); + heredoc_trimmed = + vim_strnsave(theline, (int)(skipwhite(theline) - theline)); + } + skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); + do_concat = false; + is_heredoc = true; } - skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p)); - do_concat = false; - is_heredoc = true; } } @@ -22021,8 +22121,19 @@ trans_function_name( *pp = (char_u *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { - name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); - *pp = (char_u *)end; + if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') { + len = check_luafunc_name((const char *)end+1, true); + if (len == 0) { + EMSG2(e_invexpr2, "v:lua"); + goto theend; + } + name = xmallocz(len); + memcpy(name, end+1, len); + *pp = (char_u *)end+1+len; + } else { + name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial)); + *pp = (char_u *)end; + } if (partial != NULL) { *partial = lv.ll_tv->vval.v_partial; } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index e099de831a..2aa08e2074 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -117,6 +117,7 @@ typedef enum { VV_TYPE_BOOL, VV_ECHOSPACE, VV_EXITING, + VV_LUA, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2e8bd79c81..4725246764 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -39,6 +39,7 @@ #include "nvim/buffer_updates.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/message.h" @@ -658,10 +659,10 @@ void ex_sort(exarg_T *eap) deleted = (long)(count - (lnum - eap->line2)); if (deleted > 0) { mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted, - false); + false, kExtmarkUndo); msgmore(-deleted); } else if (deleted < 0) { - mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false); + mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false, kExtmarkUndo); } if (change_occurred || deleted != 0) { changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true); @@ -874,10 +875,12 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) * their final destination at the new text position -- webb */ last_line = curbuf->b_ml.ml_line_count; - mark_adjust_nofold(line1, line2, last_line - line2, 0L, true); + mark_adjust_nofold(line1, line2, last_line - line2, 0L, true, kExtmarkNoUndo); + extmark_adjust(curbuf, line1, line2, last_line - line2, 0L, kExtmarkNoUndo, + true); changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false); if (dest >= line2) { - mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false); + mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, false, kExtmarkNoUndo); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { foldMoveRange(&win->w_folds, line1, line2, dest); @@ -886,7 +889,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) curbuf->b_op_start.lnum = dest - num_lines + 1; curbuf->b_op_end.lnum = dest; } else { - mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false); + mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, false, + kExtmarkNoUndo); FOR_ALL_TAB_WINDOWS(tab, win) { if (win->w_buffer == curbuf) { foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2); @@ -897,7 +901,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) } curbuf->b_op_start.col = curbuf->b_op_end.col = 0; mark_adjust_nofold(last_line - num_lines + 1, last_line, - -(last_line - dest - extra), 0L, true); + -(last_line - dest - extra), 0L, true, kExtmarkNoUndo); + + u_extmark_move(curbuf, line1, line2, last_line, dest, num_lines, extra); changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false); // send update regarding the new lines that were added @@ -1281,12 +1287,14 @@ static void do_filter( if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) { if (read_linecount >= linecount) { // move all marks from old lines to new lines - mark_adjust(line1, line2, linecount, 0L, false); + mark_adjust(line1, line2, linecount, 0L, false, kExtmarkUndo); } else { // move marks from old lines to new lines, delete marks // that are in deleted lines - mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false); - mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false); + mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L, false, + kExtmarkUndo); + mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L, false, + kExtmarkUndo); } } @@ -3214,6 +3222,189 @@ static char_u *sub_parse_flags(char_u *cmd, subflags_T *subflags, return cmd; } +static void extmark_move_regmatch_single(lpos_T startpos, + lpos_T endpos, + linenr_T lnum, + int sublen) +{ + colnr_T mincol; + colnr_T endcol; + colnr_T col_amount; + + mincol = startpos.col + 1; + endcol = endpos.col + 1; + + // There are cases such as :s/^/x/ where this happens + // a delete is simply not required. + if (mincol + 1 <= endcol) { + extmark_col_adjust_delete(curbuf, + lnum, mincol + 1, endcol, kExtmarkUndo, 0); + } + + // Insert, sublen seems to be the value we need but + 1... + col_amount = sublen - 1; + extmark_col_adjust(curbuf, lnum, mincol, 0, col_amount, kExtmarkUndo); +} + +static void extmark_move_regmatch_multi(ExtmarkSubMulti s, long i) +{ + colnr_T mincol; + linenr_T u_lnum; + mincol = s.startpos.col + 1; + + linenr_T n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; + colnr_T n_after_newline_in_pat = s.endpos.col; + colnr_T n_before_newline_in_pat = mincol - s.cm_start.col; + long n_after_newline_in_sub; + if (!s.newline_in_sub) { + n_after_newline_in_sub = s.cm_end.col - s.cm_start.col; + } else { + n_after_newline_in_sub = s.cm_end.col; + } + + if (s.newline_in_pat && !s.newline_in_sub) { + // -- Delete Pattern -- + // 1. Move marks in the pattern + mincol = s.startpos.col + 1; + u_lnum = n_u_lnum; + assert(n_u_lnum == u_lnum); + extmark_copy_and_place(curbuf, + s.lnum, mincol, + u_lnum, n_after_newline_in_pat, + s.lnum, mincol, + kExtmarkUndo, true, NULL); + // 2. Move marks on last newline + mincol = mincol - n_before_newline_in_pat; + extmark_col_adjust(curbuf, + u_lnum, + n_after_newline_in_pat + 1, + -s.newline_in_pat, + mincol - n_after_newline_in_pat, + kExtmarkUndo); + // Take care of the lines after + extmark_adjust(curbuf, + u_lnum, + u_lnum, + MAXLNUM, + -s.newline_in_pat, + kExtmarkUndo, + false); + // 1. first insert the text in the substitutaion + extmark_col_adjust(curbuf, + s.lnum, + mincol + 1, + s.newline_in_sub, + n_after_newline_in_sub, + kExtmarkUndo); + + } else { + // The data in sub_obj is as if the substituons above had already taken + // place. For our extmarks they haven't as we work from the bottom of the + // buffer up. Readjust the data. + n_u_lnum = s.lnum + s.endpos.lnum - s.startpos.lnum; + n_u_lnum = n_u_lnum - s.lnum_added; + + // adjusted = L - (i-1)N + // where L = lnum value, N= lnum_added and i = iteration + linenr_T a_l_lnum = s.cm_start.lnum - ((i -1) * s.lnum_added); + linenr_T a_u_lnum = a_l_lnum + s.endpos.lnum; + assert(s.startpos.lnum == 0); + + mincol = s.startpos.col + 1; + u_lnum = n_u_lnum; + + if (!s.newline_in_pat && s.newline_in_sub) { + // -- Delete Pattern -- + // 1. Move marks in the pattern + extmark_col_adjust_delete(curbuf, + a_l_lnum, + mincol + 1, + s.endpos.col + 1, + kExtmarkUndo, + s.eol); + + extmark_adjust(curbuf, + a_u_lnum + 1, + MAXLNUM, + (long)s.newline_in_sub, + 0, + kExtmarkUndo, + false); + // 3. Insert + extmark_col_adjust(curbuf, + a_l_lnum, + mincol, + s.newline_in_sub, + (long)-mincol + 1 + n_after_newline_in_sub, + kExtmarkUndo); + } else if (s.newline_in_pat && s.newline_in_sub) { + if (s.lnum_added >= 0) { + linenr_T u_col = n_after_newline_in_pat == 0 + ? 1 : n_after_newline_in_pat; + extmark_copy_and_place(curbuf, + a_l_lnum, mincol, + a_u_lnum, u_col, + a_l_lnum, mincol, + kExtmarkUndo, true, NULL); + // 2. Move marks on last newline + mincol = mincol - (colnr_T)n_before_newline_in_pat; + extmark_col_adjust(curbuf, + a_u_lnum, + (colnr_T)(n_after_newline_in_pat + 1), + -s.newline_in_pat, + mincol - n_after_newline_in_pat, + kExtmarkUndo); + // TODO(timeyyy): nothing to do here if lnum_added = 0 + extmark_adjust(curbuf, + a_u_lnum + 1, + MAXLNUM, + (long)s.lnum_added, + 0, + kExtmarkUndo, + false); + + extmark_col_adjust(curbuf, + a_l_lnum, + mincol + 1, + s.newline_in_sub, + (long)-mincol + n_after_newline_in_sub, + kExtmarkUndo); + } else { + mincol = s.startpos.col + 1; + a_l_lnum = s.startpos.lnum + 1; + a_u_lnum = s.endpos.lnum + 1; + extmark_copy_and_place(curbuf, + a_l_lnum, mincol, + a_u_lnum, n_after_newline_in_pat, + a_l_lnum, mincol, + kExtmarkUndo, true, NULL); + // 2. Move marks on last newline + mincol = mincol - (colnr_T)n_before_newline_in_pat; + extmark_col_adjust(curbuf, + a_u_lnum, + (colnr_T)(n_after_newline_in_pat + 1), + -s.newline_in_pat, + mincol - n_after_newline_in_pat, + kExtmarkUndo); + extmark_adjust(curbuf, + a_u_lnum, + a_u_lnum, + MAXLNUM, + s.lnum_added, + kExtmarkUndo, + false); + // 3. Insert + extmark_col_adjust(curbuf, + a_l_lnum, + mincol + 1, + s.newline_in_sub, + (long)-mincol + n_after_newline_in_sub, + kExtmarkUndo); + } + } + } +} + /// Perform a substitution from line eap->line1 to line eap->line2 using the /// command pointed to by eap->arg which should be of the form: /// @@ -3260,6 +3451,17 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, int save_ma = 0; int save_b_changed = curbuf->b_changed; bool preview = (State & CMDPREVIEW); + extmark_sub_multi_vec_t extmark_sub_multi = KV_INITIAL_VALUE; + extmark_sub_single_vec_t extmark_sub_single = KV_INITIAL_VALUE; + linenr_T no_of_lines_changed = 0; + linenr_T newline_in_pat = 0; + linenr_T newline_in_sub = 0; + + // inccommand tests fail without this check + if (!preview) { + // Required for Undo to work for extmarks. + u_save_cursor(); + } if (!global_busy) { sub_nsubs = 0; @@ -3418,6 +3620,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, // Check for a match on each line. // If preview: limit to max('cmdwinheight', viewport). linenr_T line2 = eap->line2; + for (linenr_T lnum = eap->line1; lnum <= line2 && !got_quit && !aborting() && (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh @@ -3524,6 +3727,11 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, // Note: If not first match on a line, column can't be known here current_match.start.lnum = sub_firstlnum; + // Match might be after the last line for "\n\zs" matching at + // the end of the last line. + if (lnum > curbuf->b_ml.ml_line_count) { + break; + } if (sub_firstline == NULL) { sub_firstline = vim_strsave(ml_get(sub_firstlnum)); } @@ -3871,6 +4079,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, ADJUST_SUB_FIRSTLNUM(); + // Now the trick is to replace CTRL-M chars with a real line // break. This would make it impossible to insert a CTRL-M in // the text. The line break can be avoided by preceding the @@ -3885,7 +4094,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, *p1 = NUL; // truncate up to the CR ml_append(lnum - 1, new_start, (colnr_T)(p1 - new_start + 1), false); - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false, + kExtmarkNOOP); + if (subflags.do_ask) { appended_lines(lnum - 1, 1L); } else { @@ -3912,6 +4123,44 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, current_match.end.lnum = lnum; } + // Adjust extmarks, by delete and then insert + if (!preview) { + newline_in_pat = (regmatch.endpos[0].lnum + - regmatch.startpos[0].lnum); + newline_in_sub = current_match.end.lnum - current_match.start.lnum; + if (newline_in_pat || newline_in_sub) { + ExtmarkSubMulti sub_multi; + no_of_lines_changed = newline_in_sub - newline_in_pat; + + sub_multi.newline_in_pat = newline_in_pat; + sub_multi.newline_in_sub = newline_in_sub; + sub_multi.lnum = lnum; + sub_multi.lnum_added = no_of_lines_changed; + sub_multi.cm_start = current_match.start; + sub_multi.cm_end = current_match.end; + + sub_multi.startpos = regmatch.startpos[0]; + sub_multi.endpos = regmatch.endpos[0]; + sub_multi.eol = extmark_eol_col(curbuf, lnum); + + kv_push(extmark_sub_multi, sub_multi); + // Collect information required for moving extmarks WITHOUT \n, \r + } else { + no_of_lines_changed = 0; + + if (regmatch.startpos[0].col != -1) { + ExtmarkSubSingle sub_single; + sub_single.sublen = sublen; + sub_single.lnum = lnum; + sub_single.startpos = regmatch.startpos[0]; + sub_single.endpos = regmatch.endpos[0]; + + kv_push(extmark_sub_single, sub_single); + } + } + } + + // 4. If subflags.do_all is set, find next match. // Prevent endless loop with patterns that match empty // strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g. @@ -3978,7 +4227,7 @@ skip: ml_delete(lnum, false); } mark_adjust(lnum, lnum + nmatch_tl - 1, - (long)MAXLNUM, -nmatch_tl, false); + (long)MAXLNUM, -nmatch_tl, false, kExtmarkNOOP); if (subflags.do_ask) { deleted_lines(lnum, nmatch_tl); } @@ -4154,6 +4403,35 @@ skip: } } } + if (newline_in_pat || newline_in_sub) { + long n = (long)kv_size(extmark_sub_multi); + ExtmarkSubMulti sub_multi; + if (no_of_lines_changed < 0) { + for (i = 0; i < n; i++) { + sub_multi = kv_A(extmark_sub_multi, i); + extmark_move_regmatch_multi(sub_multi, i); + } + } else { + // Move extmarks in reverse order to avoid moving marks we just moved... + for (i = 0; i < n; i++) { + sub_multi = kv_Z(extmark_sub_multi, i); + extmark_move_regmatch_multi(sub_multi, n - i); + } + } + kv_destroy(extmark_sub_multi); + } else { + long n = (long)kv_size(extmark_sub_single); + ExtmarkSubSingle sub_single; + for (i = 0; i < n; i++) { + sub_single = kv_Z(extmark_sub_single, i); + extmark_move_regmatch_single(sub_single.startpos, + sub_single.endpos, + sub_single.lnum, + sub_single.sublen); + } + + kv_destroy(extmark_sub_single); + } kv_destroy(preview_lines.subresults); @@ -5535,6 +5813,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, // We keep a special-purpose buffer around, but don't assume it exists. buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0; + cmdmod.split = 0; // disable :leftabove/botright modifiers cmdmod.tab = 0; // disable :tab modifier cmdmod.noswapfile = true; // disable swap for preview buffer // disable file info message @@ -5581,6 +5860,9 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, highest_num_line = kv_last(lines.subresults).end.lnum; col_width = log10(highest_num_line) + 1 + 3; } + } else { + // Failed to split the window, don't show 'inccommand' preview. + preview_buf = NULL; } char *str = NULL; // construct the line to show in here @@ -5593,7 +5875,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) { SubResult match = lines.subresults.items[matchidx]; - if (split && preview_buf) { + if (preview_buf) { lpos_T p_start = { 0, match.start.col }; // match starts here in preview lpos_T p_end = { 0, match.end.col }; // ... and ends here diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 6317ec77ff..f7aa8a994a 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -323,6 +323,12 @@ return { func='ex_abclear', }, { + command='cabove', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='caddbuffer', flags=bit.bor(RANGE, NOTADR, WORD1, TRLBAR), addr_type=ADDR_LINES, @@ -359,6 +365,12 @@ return { func='ex_cbuffer', }, { + command='cbelow', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='cbottom', flags=bit.bor(TRLBAR), addr_type=ADDR_LINES, @@ -1273,6 +1285,12 @@ return { func='ex_last', }, { + command='labove', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='language', flags=bit.bor(EXTRA, TRLBAR, CMDWIN), addr_type=ADDR_LINES, @@ -1309,6 +1327,12 @@ return { func='ex_cbuffer', }, { + command='lbelow', + flags=bit.bor(RANGE, TRLBAR), + addr_type=ADDR_OTHER , + func='ex_cbelow', + }, + { command='lbottom', flags=bit.bor(TRLBAR), addr_type=ADDR_LINES, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index ae3fb4fbfb..641edf4610 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -140,6 +140,31 @@ struct dbg_stuff { except_T *current_exception; }; +typedef struct { + // parsed results + exarg_T *eap; + char_u *parsed_upto; // local we've parsed up to so far + char_u *cmd; // start of command + char_u *after_modifier; + + // errors + char_u *errormsg; + + // globals that need to be updated + cmdmod_T cmdmod; + int sandbox; + int msg_silent; + int emsg_silent; + bool ex_pressedreturn; + long p_verbose; + + // other side-effects + bool set_eventignore; + long verbose_save; + int save_msg_silent; + int did_esilent; + bool did_sandbox; +} parse_state_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.c.generated.h" @@ -1201,69 +1226,74 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) } } -/* - * Execute one Ex command. - * - * If 'sourcing' is TRUE, the command will be included in the error message. - * - * 1. skip comment lines and leading space - * 2. handle command modifiers - * 3. skip over the range to find the command - * 4. parse the range - * 5. parse the command - * 6. parse arguments - * 7. switch on command name - * - * Note: "fgetline" can be NULL. - * - * This function may be called recursively! - */ -static char_u * do_one_cmd(char_u **cmdlinep, - int flags, - struct condstack *cstack, - LineGetter fgetline, - void *cookie /* argument for fgetline() */ - ) +/// Skip colons and trailing whitespace, returning a pointer to the first +/// non-colon, non-whitespace character. +// +/// @param skipleadingwhite Skip leading whitespace too +static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) { - char_u *p; - linenr_T lnum; - long n; - char_u *errormsg = NULL; /* error message */ - exarg_T ea; /* Ex command arguments */ - long verbose_save = -1; - int save_msg_scroll = msg_scroll; - int save_msg_silent = -1; - int did_esilent = 0; - int did_sandbox = FALSE; - cmdmod_T save_cmdmod; - const int save_reg_executing = reg_executing; - char_u *cmd; - int address_count = 1; + if (skipleadingwhite) { + p = skipwhite(p); + } - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; - ex_nesting_level++; + while (*p == ':') { + p = skipwhite(p + 1); + } - /* When the last file has not been edited :q has to be typed twice. */ - if (quitmore - /* avoid that a function call in 'statusline' does this */ - && !getline_equal(fgetline, cookie, get_func_line) - /* avoid that an autocommand, e.g. QuitPre, does this */ - && !getline_equal(fgetline, cookie, getnextac) - ) - --quitmore; + return (char_u *)p; +} - /* - * Reset browse, confirm, etc.. They are restored when returning, for - * recursive calls. - */ - save_cmdmod = cmdmod; - memset(&cmdmod, 0, sizeof(cmdmod)); +static void parse_state_to_global(const parse_state_T *parse_state) +{ + cmdmod = parse_state->cmdmod; + sandbox = parse_state->sandbox; + msg_silent = parse_state->msg_silent; + emsg_silent = parse_state->emsg_silent; + ex_pressedreturn = parse_state->ex_pressedreturn; + p_verbose = parse_state->p_verbose; - /* "#!anything" is handled like a comment. */ - if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') - goto doend; + if (parse_state->set_eventignore) { + set_string_option_direct( + (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + } +} + +static void parse_state_from_global(parse_state_T *parse_state) +{ + memset(parse_state, 0, sizeof(*parse_state)); + parse_state->cmdmod = cmdmod; + parse_state->sandbox = sandbox; + parse_state->msg_silent = msg_silent; + parse_state->emsg_silent = emsg_silent; + parse_state->ex_pressedreturn = ex_pressedreturn; + parse_state->p_verbose = p_verbose; +} + +// +// Parse one Ex command. +// +// This has no side-effects, except for modifying parameters +// passed in by pointer. +// +// The `out` should be zeroed, and its `ea` member initialised, +// before calling this function. +// +static bool parse_one_cmd( + char_u **cmdlinep, + parse_state_T *const out, + LineGetter fgetline, + void *fgetline_cookie) +{ + exarg_T ea = { + .line1 = 1, + .line2 = 1, + }; + *out->eap = ea; + + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { + return false; + } /* * Repeat until no more command modifiers are found. @@ -1273,70 +1303,76 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * 1. Skip comment lines and leading white space and colons. */ - while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') - ++ea.cmd; + while (*ea.cmd == ' ' + || *ea.cmd == '\t' + || *ea.cmd == ':') { + ea.cmd++; + } - /* in ex mode, an empty line works like :+ */ + // in ex mode, an empty line works like :+ if (*ea.cmd == NUL && exmode_active - && (getline_equal(fgetline, cookie, getexmodeline) - || getline_equal(fgetline, cookie, getexline)) + && (getline_equal(fgetline, fgetline_cookie, getexmodeline) + || getline_equal(fgetline, fgetline_cookie, getexline)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { ea.cmd = (char_u *)"+"; - ex_pressedreturn = true; + out->ex_pressedreturn = true; } - /* ignore comment and empty lines */ - if (*ea.cmd == '"') - goto doend; + // ignore comment and empty lines + if (*ea.cmd == '"') { + return false; + } if (*ea.cmd == NUL) { - ex_pressedreturn = true; - goto doend; + out->ex_pressedreturn = true; + return false; } /* * 2. Handle command modifiers. */ - p = skip_range(ea.cmd, NULL); + char_u *p = skip_range(ea.cmd, NULL); switch (*p) { - /* When adding an entry, also modify cmd_exists(). */ + // When adding an entry, also modify cmd_exists(). case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3)) break; - cmdmod.split |= WSP_ABOVE; + out->cmdmod.split |= WSP_ABOVE; continue; case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) { - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; } if (checkforcmd(&ea.cmd, "browse", 3)) { - cmdmod.browse = true; + out->cmdmod.browse = true; continue; } - if (!checkforcmd(&ea.cmd, "botright", 2)) + if (!checkforcmd(&ea.cmd, "botright", 2)) { break; - cmdmod.split |= WSP_BOT; + } + out->cmdmod.split |= WSP_BOT; continue; case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4)) break; - cmdmod.confirm = true; + out->cmdmod.confirm = true; continue; case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) { - cmdmod.keepmarks = true; + out->cmdmod.keepmarks = true; continue; } if (checkforcmd(&ea.cmd, "keepalt", 5)) { - cmdmod.keepalt = true; + out->cmdmod.keepalt = true; continue; } if (checkforcmd(&ea.cmd, "keeppatterns", 5)) { - cmdmod.keeppatterns = true; + out->cmdmod.keeppatterns = true; continue; } - if (!checkforcmd(&ea.cmd, "keepjumps", 5)) + if (!checkforcmd(&ea.cmd, "keepjumps", 5)) { break; - cmdmod.keepjumps = true; + } + out->cmdmod.keepjumps = true; continue; case 'f': { // only accept ":filter {pat} cmd" @@ -1346,7 +1382,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, break; } if (*p == '!') { - cmdmod.filter_force = true; + out->cmdmod.filter_force = true; p = skipwhite(p + 1); if (*p == NUL || ends_excmd(*p)) { break; @@ -1356,134 +1392,217 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (p == NULL || *p == NUL) { break; } - cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); - if (cmdmod.filter_regmatch.regprog == NULL) { + out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); + if (out->cmdmod.filter_regmatch.regprog == NULL) { break; } ea.cmd = p; continue; } - /* ":hide" and ":hide | cmd" are not modifiers */ + // ":hide" and ":hide | cmd" are not modifiers case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3) || *p == NUL || ends_excmd(*p)) break; ea.cmd = p; - cmdmod.hide = true; + out->cmdmod.hide = true; continue; case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) { - cmdmod.lockmarks = true; + out->cmdmod.lockmarks = true; continue; } - if (!checkforcmd(&ea.cmd, "leftabove", 5)) + if (!checkforcmd(&ea.cmd, "leftabove", 5)) { break; - cmdmod.split |= WSP_ABOVE; + } + out->cmdmod.split |= WSP_ABOVE; continue; case 'n': if (checkforcmd(&ea.cmd, "noautocmd", 3)) { - if (cmdmod.save_ei == NULL) { - /* Set 'eventignore' to "all". Restore the - * existing option value later. */ - cmdmod.save_ei = vim_strsave(p_ei); - set_string_option_direct( - (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + if (out->cmdmod.save_ei == NULL) { + // Set 'eventignore' to "all". Restore the + // existing option value later. + out->cmdmod.save_ei = vim_strsave(p_ei); + out->set_eventignore = true; } continue; } if (!checkforcmd(&ea.cmd, "noswapfile", 3)) { break; } - cmdmod.noswapfile = true; + out->cmdmod.noswapfile = true; continue; case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6)) break; - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) { - if (!did_sandbox) - ++sandbox; - did_sandbox = TRUE; + if (!out->did_sandbox) { + out->sandbox++; + } + out->did_sandbox = true; continue; } - if (!checkforcmd(&ea.cmd, "silent", 3)) + if (!checkforcmd(&ea.cmd, "silent", 3)) { break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - ++msg_silent; + } + if (out->save_msg_silent == -1) { + out->save_msg_silent = out->msg_silent; + } + out->msg_silent++; if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) { - /* ":silent!", but not "silent !cmd" */ + // ":silent!", but not "silent !cmd" ea.cmd = skipwhite(ea.cmd + 1); - ++emsg_silent; - ++did_esilent; + out->emsg_silent++; + out->did_esilent++; } continue; case 't': if (checkforcmd(&p, "tab", 3)) { - long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + long tabnr = get_address( + &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + if (tabnr == MAXLNUM) { - cmdmod.tab = tabpage_index(curtab) + 1; + out->cmdmod.tab = tabpage_index(curtab) + 1; } else { if (tabnr < 0 || tabnr > LAST_TAB_NR) { - errormsg = (char_u *)_(e_invrange); - goto doend; + out->errormsg = (char_u *)_(e_invrange); + return false; } - cmdmod.tab = tabnr + 1; + out->cmdmod.tab = tabnr + 1; } ea.cmd = p; continue; } - if (!checkforcmd(&ea.cmd, "topleft", 2)) + if (!checkforcmd(&ea.cmd, "topleft", 2)) { break; - cmdmod.split |= WSP_TOP; + } + out->cmdmod.split |= WSP_TOP; continue; case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3)) break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - msg_silent = 0; + if (out->save_msg_silent == -1) { + out->save_msg_silent = out->msg_silent; + } + out->msg_silent = 0; continue; case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) { - cmdmod.split |= WSP_VERT; + out->cmdmod.split |= WSP_VERT; continue; } if (!checkforcmd(&p, "verbose", 4)) break; - if (verbose_save < 0) - verbose_save = p_verbose; - if (ascii_isdigit(*ea.cmd)) - p_verbose = atoi((char *)ea.cmd); - else - p_verbose = 1; + if (out->verbose_save < 0) { + out->verbose_save = out->p_verbose; + } + if (ascii_isdigit(*ea.cmd)) { + out->p_verbose = atoi((char *)ea.cmd); + } else { + out->p_verbose = 1; + } ea.cmd = p; continue; } break; } - char_u *after_modifier = ea.cmd; - - ea.skip = (did_emsg - || got_int - || current_exception - || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); + out->after_modifier = ea.cmd; // 3. Skip over the range to find the command. Let "p" point to after it. // // We need the command to know what kind of range it uses. - cmd = ea.cmd; + out->cmd = ea.cmd; ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - p = find_command(&ea, NULL); + out->parsed_upto = find_command(&ea, NULL); + + *out->eap = ea; + + return true; +} + +/* + * Execute one Ex command. + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * + * 1. skip comment lines and leading space + * 2. handle command modifiers + * 3. skip over the range to find the command + * 4. parse the range + * 5. parse the command + * 6. parse arguments + * 7. switch on command name + * + * Note: "fgetline" can be NULL. + * + * This function may be called recursively! + */ +static char_u * do_one_cmd(char_u **cmdlinep, + int flags, + struct condstack *cstack, + LineGetter fgetline, + void *cookie /* argument for fgetline() */ + ) +{ + char_u *p; + linenr_T lnum; + long n; + char_u *errormsg = NULL; // error message + exarg_T ea; + int save_msg_scroll = msg_scroll; + parse_state_T parsed; + cmdmod_T save_cmdmod; + const int save_reg_executing = reg_executing; + + ex_nesting_level++; + + /* When the last file has not been edited :q has to be typed twice. */ + if (quitmore + /* avoid that a function call in 'statusline' does this */ + && !getline_equal(fgetline, cookie, get_func_line) + /* avoid that an autocommand, e.g. QuitPre, does this */ + && !getline_equal(fgetline, cookie, getnextac) + ) + --quitmore; + + /* + * Reset browse, confirm, etc.. They are restored when returning, for + * recursive calls. + */ + save_cmdmod = cmdmod; + memset(&cmdmod, 0, sizeof(cmdmod)); + + parse_state_from_global(&parsed); + parsed.eap = &ea; + parsed.verbose_save = -1; + parsed.save_msg_silent = -1; + parsed.did_esilent = 0; + parsed.did_sandbox = false; + bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie); + parse_state_to_global(&parsed); + + // Update locals from parse_one_cmd() + errormsg = parsed.errormsg; + p = parsed.parsed_upto; + + if (!parse_success) { + goto doend; + } + + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES @@ -1554,148 +1673,9 @@ static char_u * do_one_cmd(char_u **cmdlinep, } } - /* repeat for all ',' or ';' separated addresses */ - ea.cmd = cmd; - for (;; ) { - ea.line1 = ea.line2; - switch (ea.addr_type) { - case ADDR_LINES: - // default is current line number - ea.line2 = curwin->w_cursor.lnum; - break; - case ADDR_WINDOWS: - ea.line2 = CURRENT_WIN_NR; - break; - case ADDR_ARGUMENTS: - ea.line2 = curwin->w_arg_idx + 1; - if (ea.line2 > ARGCOUNT) { - ea.line2 = ARGCOUNT; - } - break; - case ADDR_LOADED_BUFFERS: - case ADDR_BUFFERS: - ea.line2 = curbuf->b_fnum; - break; - case ADDR_TABS: - ea.line2 = CURRENT_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - ea.line2 = 1; - break; - case ADDR_QUICKFIX: - ea.line2 = qf_get_cur_valid_idx(&ea); - break; - } - ea.cmd = skipwhite(ea.cmd); - lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip, - ea.addr_count == 0, address_count++); - if (ea.cmd == NULL) { // error detected - goto doend; - } - if (lnum == MAXLNUM) { - if (*ea.cmd == '%') { /* '%' - all lines */ - ++ea.cmd; - switch (ea.addr_type) { - case ADDR_LINES: - ea.line1 = 1; - ea.line2 = curbuf->b_ml.ml_line_count; - break; - case ADDR_LOADED_BUFFERS: { - buf_T *buf = firstbuf; - while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_next; - } - ea.line1 = buf->b_fnum; - buf = lastbuf; - while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_prev; - } - ea.line2 = buf->b_fnum; - break; - } - case ADDR_BUFFERS: - ea.line1 = firstbuf->b_fnum; - ea.line2 = lastbuf->b_fnum; - break; - case ADDR_WINDOWS: - case ADDR_TABS: - if (IS_USER_CMDIDX(ea.cmdidx)) { - ea.line1 = 1; - ea.line2 = - ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR; - } else { - // there is no Vim command which uses '%' and - // ADDR_WINDOWS or ADDR_TABS - errormsg = (char_u *)_(e_invrange); - goto doend; - } - break; - case ADDR_TABS_RELATIVE: - errormsg = (char_u *)_(e_invrange); - goto doend; - break; - case ADDR_ARGUMENTS: - if (ARGCOUNT == 0) { - ea.line1 = ea.line2 = 0; - } else { - ea.line1 = 1; - ea.line2 = ARGCOUNT; - } - break; - case ADDR_QUICKFIX: - ea.line1 = 1; - ea.line2 = qf_get_size(&ea); - if (ea.line2 == 0) { - ea.line2 = 1; - } - break; - } - ++ea.addr_count; - } - /* '*' - visual area */ - else if (*ea.cmd == '*') { - pos_T *fp; - - if (ea.addr_type != ADDR_LINES) { - errormsg = (char_u *)_(e_invrange); - goto doend; - } - - ++ea.cmd; - if (!ea.skip) { - fp = getmark('<', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line1 = fp->lnum; - fp = getmark('>', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line2 = fp->lnum; - ++ea.addr_count; - } - } - } else - ea.line2 = lnum; - ea.addr_count++; - - if (*ea.cmd == ';') { - if (!ea.skip) { - curwin->w_cursor.lnum = ea.line2; - // don't leave the cursor on an illegal line or column - check_cursor(); - } - } else if (*ea.cmd != ',') { - break; - } - ea.cmd++; - } - - /* One address given: set start and end lines */ - if (ea.addr_count == 1) { - ea.line1 = ea.line2; - /* ... but only implicit: really no address given */ - if (lnum == MAXLNUM) - ea.addr_count = 0; + ea.cmd = parsed.cmd; + if (parse_cmd_address(&ea, &errormsg) == FAIL) { + goto doend; } /* @@ -1705,9 +1685,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * Skip ':' and any white space */ - ea.cmd = skipwhite(ea.cmd); - while (*ea.cmd == ':') - ea.cmd = skipwhite(ea.cmd + 1); + ea.cmd = skip_colon_white(ea.cmd, true); /* * If we got a line, but no command, then go to the line. @@ -1776,8 +1754,8 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (!(flags & DOCMD_VERBOSE)) { // If the modifier was parsed OK the error must be in the following // command - if (after_modifier != NULL) { - append_command(after_modifier); + if (parsed.after_modifier != NULL) { + append_command(parsed.after_modifier); } else { append_command(*cmdlinep); } @@ -2245,12 +2223,12 @@ static char_u * do_one_cmd(char_u **cmdlinep, // The :try command saves the emsg_silent flag, reset it here when // ":silent! try" was used, it should only apply to :try itself. - if (ea.cmdidx == CMD_try && did_esilent > 0) { - emsg_silent -= did_esilent; + if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) { + emsg_silent -= parsed.did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } - did_esilent = 0; + parsed.did_esilent = 0; } // 7. Execute the command. @@ -2316,8 +2294,9 @@ doend: ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); - if (verbose_save >= 0) - p_verbose = verbose_save; + if (parsed.verbose_save >= 0) { + p_verbose = parsed.verbose_save; + } if (cmdmod.save_ei != NULL) { /* Restore 'eventignore' to the value before ":noautocmd". */ set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, @@ -2332,16 +2311,18 @@ doend: cmdmod = save_cmdmod; reg_executing = save_reg_executing; - if (save_msg_silent != -1) { - /* messages could be enabled for a serious error, need to check if the - * counters don't become negative */ - if (!did_emsg || msg_silent > save_msg_silent) - msg_silent = save_msg_silent; - emsg_silent -= did_esilent; - if (emsg_silent < 0) + if (parsed.save_msg_silent != -1) { + // messages could be enabled for a serious error, need to check if the + // counters don't become negative + if (!did_emsg || msg_silent > parsed.save_msg_silent) { + msg_silent = parsed.save_msg_silent; + } + emsg_silent -= parsed.did_esilent; + if (emsg_silent < 0) { emsg_silent = 0; - /* Restore msg_scroll, it's set by file I/O commands, even when no - * message is actually displayed. */ + } + // Restore msg_scroll, it's set by file I/O commands, even when no + // message is actually displayed. msg_scroll = save_msg_scroll; /* "silent reg" or "silent echo x" inside "redir" leaves msg_col @@ -2350,8 +2331,9 @@ doend: msg_col = 0; } - if (did_sandbox) - --sandbox; + if (parsed.did_sandbox) { + sandbox--; + } if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ ea.nextcmd = NULL; @@ -2361,6 +2343,160 @@ doend: return ea.nextcmd; } +// Parse the address range, if any, in "eap". +// Return FAIL and set "errormsg" or return OK. +int parse_cmd_address(exarg_T *eap, char_u **errormsg) + FUNC_ATTR_NONNULL_ALL +{ + int address_count = 1; + linenr_T lnum; + + // Repeat for all ',' or ';' separated addresses. + for (;;) { + eap->line1 = eap->line2; + switch (eap->addr_type) { + case ADDR_LINES: + // default is current line number + eap->line2 = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + eap->line2 = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + eap->line2 = curwin->w_arg_idx + 1; + if (eap->line2 > ARGCOUNT) { + eap->line2 = ARGCOUNT; + } + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + eap->line2 = curbuf->b_fnum; + break; + case ADDR_TABS: + eap->line2 = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + eap->line2 = 1; + break; + case ADDR_QUICKFIX: + eap->line2 = qf_get_cur_valid_idx(eap); + break; + } + eap->cmd = skipwhite(eap->cmd); + lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, + eap->addr_count == 0, address_count++); + if (eap->cmd == NULL) { // error detected + return FAIL; + } + if (lnum == MAXLNUM) { + if (*eap->cmd == '%') { // '%' - all lines + eap->cmd++; + switch (eap->addr_type) { + case ADDR_LINES: + eap->line1 = 1; + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_next; + } + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_prev; + } + eap->line2 = buf->b_fnum; + break; + } + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + case ADDR_TABS: + if (IS_USER_CMDIDX(eap->cmdidx)) { + eap->line1 = 1; + eap->line2 = eap->addr_type == ADDR_WINDOWS + ? LAST_WIN_NR : LAST_TAB_NR; + } else { + // there is no Vim command which uses '%' and + // ADDR_WINDOWS or ADDR_TABS + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + break; + case ADDR_TABS_RELATIVE: + *errormsg = (char_u *)_(e_invrange); + return FAIL; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) { + eap->line1 = eap->line2 = 0; + } else { + eap->line1 = 1; + eap->line2 = ARGCOUNT; + } + break; + case ADDR_QUICKFIX: + eap->line1 = 1; + eap->line2 = qf_get_size(eap); + if (eap->line2 == 0) { + eap->line2 = 1; + } + break; + } + eap->addr_count++; + } else if (*eap->cmd == '*') { + // '*' - visual area + if (eap->addr_type != ADDR_LINES) { + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + + eap->cmd++; + if (!eap->skip) { + pos_T *fp = getmark('<', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line1 = fp->lnum; + fp = getmark('>', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line2 = fp->lnum; + eap->addr_count++; + } + } + } else { + eap->line2 = lnum; + } + eap->addr_count++; + + if (*eap->cmd == ';') { + if (!eap->skip) { + curwin->w_cursor.lnum = eap->line2; + // don't leave the cursor on an illegal line or column + check_cursor(); + } + } else if (*eap->cmd != ',') { + break; + } + eap->cmd++; + } + + // One address given: set start and end lines. + if (eap->addr_count == 1) { + eap->line1 = eap->line2; + // ... but only implicit: really no address given + if (lnum == MAXLNUM) { + eap->addr_count = 0; + } + } + return OK; +} + /* * Check for an Ex command with optional tail. * If there is a match advance "pp" to the argument and return TRUE. @@ -3541,15 +3677,13 @@ const char * set_one_cmd_context( return NULL; } -/* - * skip a range specifier of the form: addr [,addr] [;addr] .. - * - * Backslashed delimiters after / or ? will be skipped, and commands will - * not be expanded between /'s and ?'s or after "'". - * - * Also skip white space and ":" characters. - * Returns the "cmd" pointer advanced to beyond the range. - */ +// Skip a range specifier of the form: addr [,addr] [;addr] .. +// +// Backslashed delimiters after / or ? will be skipped, and commands will +// not be expanded between /'s and ?'s or after "'". +// +// Also skip white space and ":" characters. +// Returns the "cmd" pointer advanced to beyond the range. char_u *skip_range( const char_u *cmd, int *ctx // pointer to xp_context or NULL @@ -3580,9 +3714,8 @@ char_u *skip_range( ++cmd; } - /* Skip ":" and white space. */ - while (*cmd == ':') - cmd = skipwhite(cmd + 1); + // Skip ":" and white space. + cmd = skip_colon_white(cmd, false); return (char_u *)cmd; } @@ -3750,8 +3883,7 @@ static linenr_T get_address(exarg_T *eap, curwin->w_cursor.col = 0; } searchcmdlen = 0; - if (!do_search(NULL, c, cmd, 1L, - SEARCH_HIS | SEARCH_MSG, NULL, NULL)) { + if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3788,8 +3920,7 @@ static linenr_T get_address(exarg_T *eap, pos.coladd = 0; if (searchit(curwin, curbuf, &pos, NULL, *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, - i, (linenr_T)0, NULL, NULL) != FAIL) { + (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) { lnum = pos.lnum; } else { cmd = NULL; @@ -8195,6 +8326,7 @@ static void ex_normal(exarg_T *eap) int save_insertmode = p_im; int save_finish_op = finish_op; long save_opcount = opcount; + const int save_reg_executing = reg_executing; char_u *arg = NULL; int l; char_u *p; @@ -8289,7 +8421,8 @@ static void ex_normal(exarg_T *eap) p_im = save_insertmode; finish_op = save_finish_op; opcount = save_opcount; - msg_didout |= save_msg_didout; /* don't reset msg_didout now */ + reg_executing = save_reg_executing; + msg_didout |= save_msg_didout; // don't reset msg_didout now /* Restore the state (needed when called from a function executed for * 'indentexpr'). Update the mouse and cursor, they may have changed. */ @@ -10139,6 +10272,17 @@ static void ex_folddo(exarg_T *eap) ml_clearmarked(); // clear rest of the marks } +// Returns true if the supplied Ex cmdidx is for a location list command +// instead of a quickfix command. +bool is_loclist_cmd(int cmdidx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (cmdidx < 0 || cmdidx >= CMD_SIZE) { + return false; + } + return cmdnames[cmdidx].cmd_name[0] == 'l'; +} + bool get_pressedreturn(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -10197,10 +10341,13 @@ bool cmd_can_preview(char_u *cmd) return false; } + // Ignore additional colons at the start... + cmd = skip_colon_white(cmd, true); + // Ignore any leading modifiers (:keeppatterns, :verbose, etc.) for (int len = modifier_len(cmd); len != 0; len = modifier_len(cmd)) { cmd += len; - cmd = skipwhite(cmd); + cmd = skip_colon_white(cmd, true); } exarg_T ea; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 5235b9e648..9e2671ca5e 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1075,7 +1075,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) int found = searchit(curwin, curbuf, &t, NULL, next_match ? FORWARD : BACKWARD, pat, s->count, search_flags, - RE_SEARCH, 0, NULL, NULL); + RE_SEARCH, NULL); emsg_off--; ui_busy_stop(); if (found) { @@ -1818,6 +1818,7 @@ static int command_line_changed(CommandLineState *s) if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { pos_T end_pos; proftime_T tm; + searchit_arg_T sia; // if there is a character waiting, search and redraw later if (char_avail()) { @@ -1844,8 +1845,10 @@ static int command_line_changed(CommandLineState *s) if (!p_hls) { search_flags += SEARCH_KEEP; } + memset(&sia, 0, sizeof(sia)); + sia.sa_tm = &tm; i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - search_flags, &tm, NULL); + search_flags, &sia); emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { @@ -1924,7 +1927,9 @@ static int command_line_changed(CommandLineState *s) // - Immediately undo the effects. State |= CMDPREVIEW; emsg_silent++; // Block error reporting as the command may be incomplete + msg_silent++; // Block messages, namely ones that prompt do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT); + msg_silent--; // Unblock messages emsg_silent--; // Unblock error reporting // Restore the window "view". diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 58e6b2ae92..fcf15638c7 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2650,6 +2650,7 @@ buf_write( */ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { FileInfo file_info; + const bool no_prepend_dot = false; if ((bkc & BKC_YES) || append) { /* "yes" */ backup_copy = TRUE; @@ -2737,6 +2738,7 @@ buf_write( int some_error = false; char_u *dirp; char_u *rootname; + char_u *p; /* * Try to make the backup in each directory in the 'bdir' option. @@ -2756,6 +2758,17 @@ buf_write( * Isolate one directory name, using an entry in 'bdir'. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + STRLEN(IObuff); + if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) { + // Ends with '//', Use Full path + if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname)) + != NULL) { + backup = (char_u *)modname((char *)p, (char *)backup_ext, + no_prepend_dot); + xfree(p); + } + } + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { some_error = TRUE; /* out of memory */ @@ -2764,10 +2777,14 @@ buf_write( FileInfo file_info_new; { - /* - * Make backup file name. - */ - backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); + // + // Make the backup file name. + // + if (backup == NULL) { + backup = (char_u *)modname((char *)rootname, (char *)backup_ext, + no_prepend_dot); + } + if (backup == NULL) { xfree(rootname); some_error = TRUE; /* out of memory */ @@ -2893,12 +2910,26 @@ nobackup: * Isolate one directory name and make the backup file name. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); - rootname = get_file_in_dir(fname, IObuff); - if (rootname == NULL) - backup = NULL; - else { - backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); - xfree(rootname); + p = IObuff + STRLEN(IObuff); + if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) { + // path ends with '//', use full path + if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname)) + != NULL) { + backup = (char_u *)modname((char *)p, (char *)backup_ext, + no_prepend_dot); + xfree(p); + } + } + + if (backup == NULL) { + rootname = get_file_in_dir(fname, IObuff); + if (rootname == NULL) { + backup = NULL; + } else { + backup = (char_u *)modname((char *)rootname, (char *)backup_ext, + no_prepend_dot); + xfree(rootname); + } } if (backup != NULL) { diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 5ce953e626..b193b4005c 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -771,6 +771,11 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) return; } + if (need_diff_redraw) { + // will update later + return; + } + // Mark all folds from top to bot as maybe-small. fold_T *fp; (void)foldFind(&wp->w_folds, top, &fp); diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 399f0671b4..c038977127 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2409,7 +2409,6 @@ int inchar( did_outofmem_msg = FALSE; /* display out of memory message (again) */ did_swapwrite_msg = FALSE; /* display swap file write error again */ } - undo_off = FALSE; /* restart undo now */ // Get a character from a script file if there is one. // If interrupted: Stop reading script files, close them all. diff --git a/src/nvim/globals.h b/src/nvim/globals.h index c3d1a4d40b..15ad6d8767 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -400,11 +400,6 @@ EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */ EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with mouse dragging */ -/* Value set from 'diffopt'. */ -EXTERN int diff_context INIT(= 6); /* context for folds */ -EXTERN int diff_foldcolumn INIT(= 2); /* 'foldcolumn' for diff mode */ -EXTERN int diff_need_scrollbind INIT(= FALSE); - /* The root of the menu hierarchy. */ EXTERN vimmenu_T *root_menu INIT(= NULL); /* @@ -768,7 +763,6 @@ EXTERN int did_outofmem_msg INIT(= false); // set after out of memory msg EXTERN int did_swapwrite_msg INIT(= false); // set after swap write error msg -EXTERN int undo_off INIT(= false); // undo switched off for now EXTERN int global_busy INIT(= 0); // set when :global is executing EXTERN int listcmd_busy INIT(= false); // set when :argdo, :windo or // :bufdo is executing diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 83ee89b2a1..c96f07ed89 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -321,18 +321,26 @@ int hl_combine_attr(int char_attr, int prim_attr) if (spell_aep.cterm_fg_color > 0) { new_en.cterm_fg_color = spell_aep.cterm_fg_color; + new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); } if (spell_aep.cterm_bg_color > 0) { new_en.cterm_bg_color = spell_aep.cterm_bg_color; + new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); } if (spell_aep.rgb_fg_color >= 0) { new_en.rgb_fg_color = spell_aep.rgb_fg_color; + new_en.rgb_ae_attr &= ((~HL_FG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_FG_INDEXED)); } if (spell_aep.rgb_bg_color >= 0) { new_en.rgb_bg_color = spell_aep.rgb_bg_color; + new_en.rgb_ae_attr &= ((~HL_BG_INDEXED) + | (spell_aep.rgb_ae_attr & HL_BG_INDEXED)); } if (spell_aep.rgb_sp_color >= 0) { @@ -422,6 +430,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) cattrs.cterm_bg_color = fattrs.cterm_bg_color; cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color, fattrs.cterm_bg_color); + cattrs.rgb_ae_attr &= ~(HL_FG_INDEXED | HL_BG_INDEXED); } else { cattrs = fattrs; if (ratio >= 50) { @@ -435,6 +444,8 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) } else { cattrs.rgb_sp_color = -1; } + + cattrs.rgb_ae_attr &= ~HL_BG_INDEXED; } cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color, fattrs.rgb_bg_color); @@ -611,6 +622,14 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) } if (use_rgb) { + if (mask & HL_FG_INDEXED) { + PUT(hl, "fg_indexed", BOOLEAN_OBJ(true)); + } + + if (mask & HL_BG_INDEXED) { + PUT(hl, "bg_indexed", BOOLEAN_OBJ(true)); + } + if (ae.rgb_fg_color != -1) { PUT(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color)); } diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 255699c8e0..36f3181674 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -19,6 +19,8 @@ typedef enum { HL_STANDOUT = 0x20, HL_STRIKETHROUGH = 0x40, HL_NOCOMBINE = 0x80, + HL_BG_INDEXED = 0x0100, + HL_FG_INDEXED = 0x0200, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h index 33aeff1d89..bef37f8ba9 100644 --- a/src/nvim/lib/kbtree.h +++ b/src/nvim/lib/kbtree.h @@ -25,6 +25,12 @@ * SUCH DAMAGE. */ +// Gotchas +// ------- +// +// if you delete from a kbtree while iterating over it you must use +// kb_del_itr and not kb_del otherwise the iterator might point to freed memory. + #ifndef NVIM_LIB_KBTREE_H #define NVIM_LIB_KBTREE_H diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9665655e74..44fe60e9c8 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -377,6 +377,19 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) nlua_pop_typval_table_processing_end: break; } + case LUA_TUSERDATA: { + nlua_pushref(lstate, nlua_nil_ref); + bool is_nil = lua_rawequal(lstate, -2, -1); + lua_pop(lstate, 1); + if (is_nil) { + cur.tv->v_type = VAR_SPECIAL; + cur.tv->vval.v_special = kSpecialVarNull; + } else { + EMSG(_("E5101: Cannot convert given lua type")); + ret = false; + } + break; + } default: { EMSG(_("E5101: Cannot convert given lua type")); ret = false; @@ -401,10 +414,18 @@ nlua_pop_typval_table_processing_end: return ret; } +static bool typval_conv_special = false; + #define TYPVAL_ENCODE_ALLOW_SPECIALS true #define TYPVAL_ENCODE_CONV_NIL(tv) \ - lua_pushnil(lstate) + do { \ + if (typval_conv_special) { \ + lua_pushnil(lstate); \ + } else { \ + nlua_pushref(lstate, nlua_nil_ref); \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ lua_pushboolean(lstate, (bool)(num)) @@ -439,7 +460,13 @@ nlua_pop_typval_table_processing_end: lua_createtable(lstate, 0, 0) #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ - nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary) + do { \ + if (typval_conv_special) { \ + nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ + } else { \ + lua_createtable(lstate, 0, 0); \ + } \ + } while (0) #define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ do { \ @@ -548,9 +575,11 @@ nlua_pop_typval_table_processing_end: /// @param[in] tv typval_T to convert. /// /// @return true in case of success, false otherwise. -bool nlua_push_typval(lua_State *lstate, typval_T *const tv) +bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) { + typval_conv_special = special; const int initial_size = lua_gettop(lstate); + if (!lua_checkstack(lstate, initial_size + 2)) { emsgf(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; @@ -708,7 +737,11 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) { switch (obj.type) { case kObjectTypeNil: { - lua_pushnil(lstate); + if (special) { + lua_pushnil(lstate); + } else { + nlua_pushref(lstate, nlua_nil_ref); + } break; } case kObjectTypeLuaRef: { @@ -1142,6 +1175,19 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) break; } + case LUA_TUSERDATA: { + nlua_pushref(lstate, nlua_nil_ref); + bool is_nil = lua_rawequal(lstate, -2, -1); + lua_pop(lstate, 1); + if (is_nil) { + *cur.obj = NIL; + } else { + api_set_error(err, kErrorTypeValidation, + "Cannot convert userdata"); + } + break; + } + default: { type_error: api_set_error(err, kErrorTypeValidation, diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 127458fe39..093c130c5f 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -12,6 +12,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/vim.h" #include "nvim/ex_getln.h" #include "nvim/ex_cmds2.h" @@ -47,9 +48,6 @@ typedef struct { # include "lua/executor.c.generated.h" #endif -/// Name of the run code for use in messages -#define NLUA_EVAL_NAME "<VimL compiled string>" - /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -295,6 +293,17 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // in_fast_event lua_pushcfunction(lstate, &nlua_in_fast_event); lua_setfield(lstate, -2, "in_fast_event"); + // call + lua_pushcfunction(lstate, &nlua_call); + lua_setfield(lstate, -2, "call"); + + // rpcrequest + lua_pushcfunction(lstate, &nlua_rpcrequest); + lua_setfield(lstate, -2, "rpcrequest"); + + // rpcnotify + lua_pushcfunction(lstate, &nlua_rpcnotify); + lua_setfield(lstate, -2, "rpcnotify"); // vim.loop luv_set_loop(lstate, &main_loop.uv); @@ -311,6 +320,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "luv"); lua_pop(lstate, 3); + // vim.NIL + lua_newuserdata(lstate, 0); + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_nil_tostring); + lua_setfield(lstate, -2, "__tostring"); + lua_setmetatable(lstate, -2); + nlua_nil_ref = nlua_ref(lstate, -1); + lua_setfield(lstate, -2, "NIL"); + // internal vim._treesitter... API nlua_add_treesitter(lstate); @@ -376,29 +394,6 @@ static lua_State *nlua_enter(void) return lstate; } -/// Execute lua string -/// -/// @param[in] str String to execute. -/// @param[out] ret_tv Location where result will be saved. -/// -/// @return Result of the execution. -void executor_exec_lua(const String str, typval_T *const ret_tv) - FUNC_ATTR_NONNULL_ALL -{ - lua_State *const lstate = nlua_enter(); - - if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return; - } - - nlua_pop_typval(lstate, ret_tv); -} - static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -539,6 +534,125 @@ int nlua_in_fast_event(lua_State *lstate) return 1; } +int nlua_call(lua_State *lstate) +{ + Error err = ERROR_INIT; + size_t name_len; + const char_u *name = (const char_u *)luaL_checklstring(lstate, 1, &name_len); + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "vimL function"); + } + + int nargs = lua_gettop(lstate)-1; + if (nargs > MAX_FUNC_ARGS) { + return luaL_error(lstate, "Function called with too many arguments"); + } + + typval_T vim_args[MAX_FUNC_ARGS + 1]; + int i = 0; // also used for freeing the variables + for (; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+2); + if (!nlua_pop_typval(lstate, &vim_args[i])) { + api_set_error(&err, kErrorTypeException, + "error converting argument %d", i+1); + goto free_vim_args; + } + } + + TRY_WRAP({ + // TODO(bfredl): this should be simplified in error handling refactor + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + did_emsg = false; + + try_start(); + typval_T rettv; + int dummy; + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (TRY_WRAP) to capture abort-causing non-exception errors. + (void)call_func(name, (int)name_len, &rettv, nargs, + vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, NULL); + if (!try_end(&err)) { + nlua_push_typval(lstate, &rettv, false); + } + tv_clear(&rettv); + }); + +free_vim_args: + while (i > 0) { + tv_clear(&vim_args[--i]); + } + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + return 1; +} + +static int nlua_rpcrequest(lua_State *lstate) +{ + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "rpcrequest"); + } + return nlua_rpc(lstate, true); +} + +static int nlua_rpcnotify(lua_State *lstate) +{ + return nlua_rpc(lstate, false); +} + +static int nlua_rpc(lua_State *lstate, bool request) +{ + size_t name_len; + uint64_t chan_id = (uint64_t)luaL_checkinteger(lstate, 1); + const char *name = luaL_checklstring(lstate, 2, &name_len); + int nargs = lua_gettop(lstate)-2; + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + + for (int i = 0; i < nargs; i++) { + lua_pushvalue(lstate, (int)i+3); + ADD(args, nlua_pop_Object(lstate, false, &err)); + if (ERROR_SET(&err)) { + api_free_array(args); + goto check_err; + } + } + + if (request) { + Object result = rpc_send_call(chan_id, name, args, &err); + if (!ERROR_SET(&err)) { + nlua_push_Object(lstate, result, false); + api_free_object(result); + } + } else { + if (!rpc_send_event(chan_id, name, args)) { + api_set_error(&err, kErrorTypeValidation, + "Invalid channel: %"PRIu64, chan_id); + } + } + +check_err: + if (ERROR_SET(&err)) { + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + return lua_error(lstate); + } + + return request ? 1 : 0; +} + +static int nlua_nil_tostring(lua_State *lstate) +{ + lua_pushstring(lstate, "vim.NIL"); + return 1; +} + + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// @@ -592,10 +706,6 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - lua_State *const lstate = nlua_enter(); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); #define EVALHEADER "local _A=select(1,...) return (" const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; char *lcmd; @@ -608,30 +718,71 @@ void executor_eval_lua(const String str, typval_T *const arg, memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size); lcmd[lcmd_len - 1] = ')'; #undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return; - } + typval_exec_lua(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); + if (lcmd != (char *)IObuff) { xfree(lcmd); } +} - if (arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); +void executor_call_lua(const char *str, size_t len, typval_T *const args, + int argcount, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ +#define CALLHEADER "return " +#define CALLSUFFIX "(...)" + const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; } else { - nlua_push_typval(lstate, arg); + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, CALLHEADER, sizeof(CALLHEADER) - 1); + memcpy(lcmd + sizeof(CALLHEADER) - 1, str, len); + memcpy(lcmd + sizeof(CALLHEADER) - 1 + len, CALLSUFFIX, + sizeof(CALLSUFFIX) - 1); +#undef CALLHEADER +#undef CALLSUFFIX + + typval_exec_lua(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); + + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } +} + +static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name, + typval_T *const args, int argcount, bool special, + typval_T *ret_tv) +{ + if (check_restricted() || check_secure()) { + ret_tv->v_type = VAR_NUMBER; + ret_tv->vval.v_number = 0; + return; + } + + lua_State *const lstate = nlua_enter(); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, name)) { + nlua_error(lstate, _("E5107: Error loading lua %.*s")); + return; } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + + for (int i = 0; i < argcount; i++) { + if (args[i].v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, &args[i], special); + } + } + if (lua_pcall(lstate, argcount, ret_tv ? 1 : 0, 0)) { + nlua_error(lstate, _("E5108: Error executing lua %.*s")); return; } - nlua_pop_typval(lstate, ret_tv); + if (ret_tv) { + nlua_pop_typval(lstate, ret_tv); + } } /// Execute lua string @@ -717,9 +868,8 @@ void ex_lua(exarg_T *const eap) xfree(code); return; } - typval_T tv = { .v_type = VAR_UNKNOWN }; - executor_exec_lua((String) { .data = code, .size = len }, &tv); - tv_clear(&tv); + typval_exec_lua(code, len, ":lua", NULL, 0, false, NULL); + xfree(code); } @@ -757,8 +907,8 @@ void ex_luado(exarg_T *const eap) #undef DOSTART #undef DOEND - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, ":luado")) { + nlua_error(lstate, _("E5109: Error loading lua: %.*s")); if (lcmd_len >= IOSIZE) { xfree(lcmd); } @@ -768,7 +918,7 @@ void ex_luado(exarg_T *const eap) xfree(lcmd); } if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + nlua_error(lstate, _("E5110: Error executing lua: %.*s")); return; } for (linenr_T l = eap->line1; l <= eap->line2; l++) { @@ -779,7 +929,7 @@ void ex_luado(exarg_T *const eap) lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); lua_pushnumber(lstate, (lua_Number)l); if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + nlua_error(lstate, _("E5111: Error calling lua: %.*s")); break; } if (lua_isstring(lstate, -1)) { diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 8d356a5600..32f66b629c 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -12,6 +12,8 @@ // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; +EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); + #define set_api_error(s, err) \ do { \ Error *err_ = (err); \ diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index b67762e48e..1665a55aff 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -165,6 +165,19 @@ end --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. --- +--- Example: To remove ANSI color codes when pasting: +--- <pre> +--- vim.paste = (function(overridden) +--- return function(lines, phase) +--- for i,line in ipairs(lines) do +--- -- Scrub ANSI color codes from paste input. +--- lines[i] = line:gsub('\27%[[0-9;mK]+', '') +--- end +--- overridden(lines, phase) +--- end +--- end)(vim.paste) +--- </pre> +--- --@see |paste| --- --@param lines |readfile()|-style list of lines to paste. |channel-lines| @@ -192,8 +205,11 @@ paste = (function() local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub. vim.api.nvim_input(line1) vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then -- Else: discard remaining cmdline-mode chunks. - if phase < 2 and mode ~= 'i' and mode ~= 'R' and mode ~= 't' then + elseif mode ~= 'c' then + if phase < 2 and mode:find('^[vV\22sS\19]') then + vim.api.nvim_command([[exe "normal! \<Del>"]]) + vim.api.nvim_put(lines, 'c', false, true) + elseif phase < 2 and not mode:find('^[iRt]') then vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') @@ -239,8 +255,26 @@ local function __index(t, key) -- Expose all `vim.shared` functions on the `vim` module. t[key] = require('vim.shared')[key] return t[key] + elseif require('vim.uri')[key] ~= nil then + -- Expose all `vim.uri` functions on the `vim` module. + t[key] = require('vim.uri')[key] + return t[key] + elseif key == 'lsp' then + t.lsp = require('vim.lsp') + return t.lsp + end +end + + +-- vim.fn.{func}(...) +local function _fn_index(t, key) + local function _fn(...) + return vim.call(key, ...) end + t[key] = _fn + return _fn end +local fn = setmetatable({}, {__index=_fn_index}) local module = { _update_package_paths = _update_package_paths, @@ -249,6 +283,7 @@ local module = { _system = _system, paste = paste, schedule_wrap = schedule_wrap, + fn=fn, } setmetatable(module, { diff --git a/src/nvim/main.c b/src/nvim/main.c index e0a1e60fc0..e39eec4038 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -27,6 +27,7 @@ #include "nvim/highlight.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" +#include "nvim/lua/executor.h" #ifdef HAVE_LOCALE_H # include <locale.h> #endif diff --git a/src/nvim/mark.c b/src/nvim/mark.c index e8f1651a6e..e5070f23ff 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -296,17 +296,17 @@ pos_T *movechangelist(int count) * - NULL if there is no mark called 'c'. * - -1 if mark is in other file and jumped there (only if changefile is TRUE) */ -pos_T *getmark_buf(buf_T *buf, int c, int changefile) +pos_T *getmark_buf(buf_T *buf, int c, bool changefile) { return getmark_buf_fnum(buf, c, changefile, NULL); } -pos_T *getmark(int c, int changefile) +pos_T *getmark(int c, bool changefile) { return getmark_buf_fnum(curbuf, c, changefile, NULL); } -pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) +pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum) { pos_T *posp; pos_T *startp, *endp; @@ -905,9 +905,10 @@ void mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_after, - bool end_temp) + bool end_temp, + ExtmarkOp op) { - mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp); + mark_adjust_internal(line1, line2, amount, amount_after, true, end_temp, op); } // mark_adjust_nofold() does the same as mark_adjust() but without adjusting @@ -916,14 +917,16 @@ void mark_adjust(linenr_T line1, // calling foldMarkAdjust() with arguments line1, line2, amount, amount_after, // for an example of why this may be necessary, see do_move(). void mark_adjust_nofold(linenr_T line1, linenr_T line2, long amount, - long amount_after, bool end_temp) + long amount_after, bool end_temp, + ExtmarkOp op) { - mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp); + mark_adjust_internal(line1, line2, amount, amount_after, false, end_temp, op); } static void mark_adjust_internal(linenr_T line1, linenr_T line2, long amount, long amount_after, - bool adjust_folds, bool end_temp) + bool adjust_folds, bool end_temp, + ExtmarkOp op) { int i; int fnum = curbuf->b_fnum; @@ -979,6 +982,9 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, sign_mark_adjust(line1, line2, amount, amount_after); bufhl_mark_adjust(curbuf, line1, line2, amount, amount_after, end_temp); + if (op != kExtmarkNOOP) { + extmark_adjust(curbuf, line1, line2, amount, amount_after, op, end_temp); + } } /* previous context mark */ @@ -1090,7 +1096,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, // cursor is inside them. void mark_col_adjust( linenr_T lnum, colnr_T mincol, long lnum_amount, long col_amount, - int spaces_removed) + int spaces_removed, ExtmarkOp op) { int i; int fnum = curbuf->b_fnum; @@ -1110,6 +1116,13 @@ void mark_col_adjust( col_adjust(&(namedfm[i].fmark.mark)); } + // Extmarks + if (op != kExtmarkNOOP) { + // TODO(timeyyy): consider spaces_removed? (behave like a delete) + extmark_col_adjust(curbuf, lnum, mincol, lnum_amount, col_amount, + kExtmarkUndo); + } + /* last Insert position */ col_adjust(&(curbuf->b_last_insert.mark)); diff --git a/src/nvim/mark_extended.c b/src/nvim/mark_extended.c new file mode 100644 index 0000000000..01745f484d --- /dev/null +++ b/src/nvim/mark_extended.c @@ -0,0 +1,1135 @@ +// 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 + +// Implements extended marks for plugins. Each mark exists in a btree of +// lines containing btrees of columns. +// +// The btree provides efficent range lookups. +// A map of pointers to the marks is used for fast lookup by mark id. +// +// Marks are moved by calls to: extmark_col_adjust, extmark_adjust, or +// extmark_col_adjust_delete which are based on col_adjust and mark_adjust from +// mark.c +// +// Undo/Redo of marks is implemented by storing the call arguments to +// extmark_col_adjust or extmark_adjust. The list of arguments +// is applied in extmark_apply_undo. The only case where we have to +// copy extmarks is for the area being effected by a delete. +// +// Marks live in namespaces that allow plugins/users to segregate marks +// from other users. +// +// For possible ideas for efficency improvements see: +// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html +// TODO(bfredl): These ideas could be used for an enhanced btree, which +// wouldn't need separate line and column layers. +// Other implementations exist in gtk and tk toolkits. +// +// Deleting marks only happens when explicitly calling extmark_del, deleteing +// over a range of marks will only move the marks. Deleting on a mark will +// leave it in same position unless it is on the EOL of a line. + +#include <assert.h> +#include "nvim/vim.h" +#include "charset.h" +#include "nvim/mark_extended.h" +#include "nvim/memline.h" +#include "nvim/pos.h" +#include "nvim/globals.h" +#include "nvim/map.h" +#include "nvim/lib/kbtree.h" +#include "nvim/undo.h" +#include "nvim/buffer.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "mark_extended.c.generated.h" +#endif + + +/// Create or update an extmark +/// +/// must not be used during iteration! +/// @returns whether a new mark was created +int extmark_set(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, ExtmarkOp op) +{ + Extmark *extmark = extmark_from_id(buf, ns, id); + if (!extmark) { + extmark_create(buf, ns, id, lnum, col, op); + return true; + } else { + ExtmarkLine *extmarkline = extmark->line; + extmark_update(extmark, buf, ns, id, lnum, col, op, NULL); + if (kb_size(&extmarkline->items) == 0) { + kb_del(extmarklines, &buf->b_extlines, extmarkline); + extmarkline_free(extmarkline); + } + return false; + } +} + +// Remove an extmark +// Returns 0 on missing id +int extmark_del(buf_T *buf, uint64_t ns, uint64_t id, ExtmarkOp op) +{ + Extmark *extmark = extmark_from_id(buf, ns, id); + if (!extmark) { + return 0; + } + return extmark_delete(extmark, buf, ns, id, op); +} + +// Free extmarks in a ns between lines +// if ns = 0, it means clear all namespaces +void extmark_clear(buf_T *buf, uint64_t ns, + linenr_T l_lnum, linenr_T u_lnum, ExtmarkOp undo) +{ + if (!buf->b_extmark_ns) { + return; + } + + bool marks_cleared = false; + if (undo == kExtmarkUndo) { + // Copy marks that would be effected by clear + u_extmark_copy(buf, ns, l_lnum, 0, u_lnum, MAXCOL); + } + + bool all_ns = ns == 0 ? true : false; + ExtmarkNs *ns_obj; + if (!all_ns) { + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + if (!ns_obj) { + // nothing to do + return; + } + } + + FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, { + if (extmark->ns_id == ns || all_ns) { + marks_cleared = true; + if (all_ns) { + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, extmark->ns_id); + } else { + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + } + pmap_del(uint64_t)(ns_obj->map, extmark->mark_id); + kb_del_itr(markitems, &extmarkline->items, &mitr); + } + }); + if (kb_size(&extmarkline->items) == 0) { + kb_del_itr(extmarklines, &buf->b_extlines, &itr); + extmarkline_free(extmarkline); + } + }); + + // Record the undo for the actual move + if (marks_cleared && undo == kExtmarkUndo) { + u_extmark_clear(buf, ns, l_lnum, u_lnum); + } +} + +// Returns the position of marks between a range, +// marks found at the start or end index will be included, +// if upper_lnum or upper_col are negative the buffer +// will be searched to the start, or end +// dir can be set to control the order of the array +// amount = amount of marks to find or -1 for all +ExtmarkArray extmark_get(buf_T *buf, uint64_t ns, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col, + int64_t amount, bool reverse) +{ + ExtmarkArray array = KV_INITIAL_VALUE; + // Find all the marks + if (!reverse) { + FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, { + if (extmark->ns_id == ns) { + kv_push(array, extmark); + if (kv_size(array) == (size_t)amount) { + return array; + } + } + }) + } else { + FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, { + if (extmark->ns_id == ns) { + kv_push(array, extmark); + if (kv_size(array) == (size_t)amount) { + return array; + } + } + }) + } + return array; +} + +static void extmark_create(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, ExtmarkOp op) +{ + if (!buf->b_extmark_ns) { + buf->b_extmark_ns = pmap_new(uint64_t)(); + } + ExtmarkNs *ns_obj = NULL; + ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + // Initialize a new namespace for this buffer + if (!ns_obj) { + ns_obj = xmalloc(sizeof(ExtmarkNs)); + ns_obj->map = pmap_new(uint64_t)(); + pmap_put(uint64_t)(buf->b_extmark_ns, ns, ns_obj); + } + + // Create or get a line + ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, true); + // Create and put mark on the line + extmark_put(col, id, extmarkline, ns); + + // Marks do not have stable address so we have to look them up + // by using the line instead of the mark + pmap_put(uint64_t)(ns_obj->map, id, extmarkline); + if (op != kExtmarkNoUndo) { + u_extmark_set(buf, ns, id, lnum, col, kExtmarkSet); + } + + // Set a free id so extmark_free_id_get works + extmark_free_id_set(ns_obj, id); +} + +// update the position of an extmark +// to update while iterating pass the markitems itr +static void extmark_update(Extmark *extmark, buf_T *buf, + uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, + ExtmarkOp op, kbitr_t(markitems) *mitr) +{ + assert(op != kExtmarkNOOP); + if (op != kExtmarkNoUndo) { + u_extmark_update(buf, ns, id, extmark->line->lnum, extmark->col, + lnum, col); + } + ExtmarkLine *old_line = extmark->line; + // Move the mark to a new line and update column + if (old_line->lnum != lnum) { + ExtmarkLine *ref_line = extmarkline_ref(buf, lnum, true); + extmark_put(col, id, ref_line, ns); + // Update the hashmap + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + pmap_put(uint64_t)(ns_obj->map, id, ref_line); + // Delete old mark + if (mitr != NULL) { + kb_del_itr(markitems, &(old_line->items), mitr); + } else { + kb_del(markitems, &old_line->items, *extmark); + } + // Just update the column + } else { + if (mitr != NULL) { + // The btree stays organized during iteration with kbitr_t + extmark->col = col; + } else { + // Keep the btree in order + kb_del(markitems, &old_line->items, *extmark); + extmark_put(col, id, old_line, ns); + } + } +} + +static int extmark_delete(Extmark *extmark, + buf_T *buf, + uint64_t ns, + uint64_t id, + ExtmarkOp op) +{ + if (op != kExtmarkNoUndo) { + u_extmark_set(buf, ns, id, extmark->line->lnum, extmark->col, + kExtmarkDel); + } + + // Remove our key from the namespace + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + pmap_del(uint64_t)(ns_obj->map, id); + + // Remove the mark mark from the line + ExtmarkLine *extmarkline = extmark->line; + kb_del(markitems, &extmarkline->items, *extmark); + // Remove the line if there are no more marks in the line + if (kb_size(&extmarkline->items) == 0) { + kb_del(extmarklines, &buf->b_extlines, extmarkline); + extmarkline_free(extmarkline); + } + return true; +} + +// Lookup an extmark by id +Extmark *extmark_from_id(buf_T *buf, uint64_t ns, uint64_t id) +{ + if (!buf->b_extmark_ns) { + return NULL; + } + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + if (!ns_obj || !kh_size(ns_obj->map->table)) { + return NULL; + } + ExtmarkLine *extmarkline = pmap_get(uint64_t)(ns_obj->map, id); + if (!extmarkline) { + return NULL; + } + + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, 0, MAXCOL, { + if (extmark->ns_id == ns + && extmark->mark_id == id) { + return extmark; + } + }) + return NULL; +} + +// Lookup an extmark by position +Extmark *extmark_from_pos(buf_T *buf, uint64_t ns, linenr_T lnum, colnr_T col) +{ + if (!buf->b_extmark_ns) { + return NULL; + } + FOR_ALL_EXTMARKS(buf, ns, lnum, col, lnum, col, { + if (extmark->ns_id == ns) { + if (extmark->col == col) { + return extmark; + } + } + }) + return NULL; +} + +// Returns an avaliable id in a namespace +uint64_t extmark_free_id_get(buf_T *buf, uint64_t ns) +{ + if (!buf->b_extmark_ns) { + return 1; + } + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, ns); + if (!ns_obj) { + return 1; + } + return ns_obj->free_id; +} + +// Set the next free id in a namesapce +static void extmark_free_id_set(ExtmarkNs *ns_obj, uint64_t id) +{ + // Simply Heurstic, the largest id + 1 + ns_obj->free_id = id + 1; +} + +// free extmarks from the buffer +void extmark_free_all(buf_T *buf) +{ + if (!buf->b_extmark_ns) { + return; + } + + uint64_t ns; + ExtmarkNs *ns_obj; + + FOR_ALL_EXTMARKLINES(buf, 1, MAXLNUM, { + kb_del_itr(extmarklines, &buf->b_extlines, &itr); + extmarkline_free(extmarkline); + }) + + map_foreach(buf->b_extmark_ns, ns, ns_obj, { + (void)ns; + pmap_free(uint64_t)(ns_obj->map); + xfree(ns_obj); + }); + + pmap_free(uint64_t)(buf->b_extmark_ns); + buf->b_extmark_ns = NULL; + + // k?_init called to set pointers to NULL + kb_destroy(extmarklines, (&buf->b_extlines)); + kb_init(&buf->b_extlines); + + kv_destroy(buf->b_extmark_move_space); + kv_init(buf->b_extmark_move_space); +} + + +// Save info for undo/redo of set marks +static void u_extmark_set(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T lnum, colnr_T col, UndoObjectType undo_type) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkSet set; + set.ns_id = ns; + set.mark_id = id; + set.lnum = lnum; + set.col = col; + + ExtmarkUndoObject undo = { .type = undo_type, + .data.set = set }; + + kv_push(uhp->uh_extmark, undo); +} + +// Save info for undo/redo of deleted marks +static void u_extmark_update(buf_T *buf, uint64_t ns, uint64_t id, + linenr_T old_lnum, colnr_T old_col, + linenr_T lnum, colnr_T col) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkUpdate update; + update.ns_id = ns; + update.mark_id = id; + update.old_lnum = old_lnum; + update.old_col = old_col; + update.lnum = lnum; + update.col = col; + + ExtmarkUndoObject undo = { .type = kExtmarkUpdate, + .data.update = update }; + kv_push(uhp->uh_extmark, undo); +} + +// Hueristic works only for when the user is typing in insert mode +// - Instead of 1 undo object for each char inserted, +// we create 1 undo objet for all text inserted before the user hits esc +// Return True if we compacted else False +static bool u_compact_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol, + long lnum_amount, long col_amount) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return false; + } + + if (kv_size(uhp->uh_extmark) < 1) { + return false; + } + // Check the last action + ExtmarkUndoObject object = kv_last(uhp->uh_extmark); + + if (object.type != kColAdjust) { + return false; + } + ColAdjust undo = object.data.col_adjust; + bool compactable = false; + + if (!undo.lnum_amount && !lnum_amount) { + if (undo.lnum == lnum) { + if ((undo.mincol + undo.col_amount) >= mincol) { + compactable = true; + } } } + + if (!compactable) { + return false; + } + + undo.col_amount = undo.col_amount + col_amount; + ExtmarkUndoObject new_undo = { .type = kColAdjust, + .data.col_adjust = undo }; + kv_last(uhp->uh_extmark) = new_undo; + return true; +} + +// Save col_adjust info so we can undo/redo +void u_extmark_col_adjust(buf_T *buf, linenr_T lnum, colnr_T mincol, + long lnum_amount, long col_amount) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + if (!u_compact_col_adjust(buf, lnum, mincol, lnum_amount, col_amount)) { + ColAdjust col_adjust; + col_adjust.lnum = lnum; + col_adjust.mincol = mincol; + col_adjust.lnum_amount = lnum_amount; + col_adjust.col_amount = col_amount; + + ExtmarkUndoObject undo = { .type = kColAdjust, + .data.col_adjust = col_adjust }; + + kv_push(uhp->uh_extmark, undo); + } +} + +// Save col_adjust_delete info so we can undo/redo +void u_extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, + colnr_T mincol, colnr_T endcol, int eol) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ColAdjustDelete col_adjust_delete; + col_adjust_delete.lnum = lnum; + col_adjust_delete.mincol = mincol; + col_adjust_delete.endcol = endcol; + col_adjust_delete.eol = eol; + + ExtmarkUndoObject undo = { .type = kColAdjustDelete, + .data.col_adjust_delete = col_adjust_delete }; + + kv_push(uhp->uh_extmark, undo); +} + +// Save adjust info so we can undo/redo +static void u_extmark_adjust(buf_T * buf, linenr_T line1, linenr_T line2, + long amount, long amount_after) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + Adjust adjust; + adjust.line1 = line1; + adjust.line2 = line2; + adjust.amount = amount; + adjust.amount_after = amount_after; + + ExtmarkUndoObject undo = { .type = kLineAdjust, + .data.adjust = adjust }; + + kv_push(uhp->uh_extmark, undo); +} + +// save info to undo/redo a :move +void u_extmark_move(buf_T *buf, linenr_T line1, linenr_T line2, + linenr_T last_line, linenr_T dest, linenr_T num_lines, + linenr_T extra) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + AdjustMove move; + move.line1 = line1; + move.line2 = line2; + move.last_line = last_line; + move.dest = dest; + move.num_lines = num_lines; + move.extra = extra; + + ExtmarkUndoObject undo = { .type = kAdjustMove, + .data.move = move }; + + kv_push(uhp->uh_extmark, undo); +} + +// copy extmarks data between range, useful when we cannot simply reverse +// the operation. This will do nothing on redo, enforces correct position when +// undo. +// if ns = 0, it means copy all namespaces +void u_extmark_copy(buf_T *buf, uint64_t ns, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + bool all_ns = ns == 0 ? true : false; + + ExtmarkCopy copy; + ExtmarkUndoObject undo; + FOR_ALL_EXTMARKS(buf, 1, l_lnum, l_col, u_lnum, u_col, { + if (all_ns || extmark->ns_id == ns) { + copy.ns_id = extmark->ns_id; + copy.mark_id = extmark->mark_id; + copy.lnum = extmark->line->lnum; + copy.col = extmark->col; + + undo.data.copy = copy; + undo.type = kExtmarkCopy; + kv_push(uhp->uh_extmark, undo); + } + }); +} + +void u_extmark_copy_place(buf_T *buf, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col, + linenr_T p_lnum, colnr_T p_col) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkCopyPlace copy_place; + copy_place.l_lnum = l_lnum; + copy_place.l_col = l_col; + copy_place.u_lnum = u_lnum; + copy_place.u_col = u_col; + copy_place.p_lnum = p_lnum; + copy_place.p_col = p_col; + + ExtmarkUndoObject undo = { .type = kExtmarkCopyPlace, + .data.copy_place = copy_place }; + + kv_push(uhp->uh_extmark, undo); +} + +// Save info for undo/redo of extmark_clear +static void u_extmark_clear(buf_T *buf, uint64_t ns, + linenr_T l_lnum, linenr_T u_lnum) +{ + u_header_T *uhp = u_force_get_undo_header(buf); + if (!uhp) { + return; + } + + ExtmarkClear clear; + clear.ns_id = ns; + clear.l_lnum = l_lnum; + clear.u_lnum = u_lnum; + + ExtmarkUndoObject undo = { .type = kExtmarkClear, + .data.clear = clear }; + kv_push(uhp->uh_extmark, undo); +} + +// undo or redo an extmark operation +void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) +{ + linenr_T lnum; + colnr_T mincol; + long lnum_amount; + long col_amount; + linenr_T line1; + linenr_T line2; + long amount; + long amount_after; + + // use extmark_col_adjust + if (undo_info.type == kColAdjust) { + // Undo + if (undo) { + lnum = (undo_info.data.col_adjust.lnum + + undo_info.data.col_adjust.lnum_amount); + lnum_amount = -undo_info.data.col_adjust.lnum_amount; + col_amount = -undo_info.data.col_adjust.col_amount; + mincol = (undo_info.data.col_adjust.mincol + + (colnr_T)undo_info.data.col_adjust.col_amount); + // Redo + } else { + lnum = undo_info.data.col_adjust.lnum; + col_amount = undo_info.data.col_adjust.col_amount; + lnum_amount = undo_info.data.col_adjust.lnum_amount; + mincol = undo_info.data.col_adjust.mincol; + } + extmark_col_adjust(curbuf, + lnum, mincol, lnum_amount, col_amount, kExtmarkNoUndo); + // use extmark_col_adjust_delete + } else if (undo_info.type == kColAdjustDelete) { + if (undo) { + mincol = undo_info.data.col_adjust_delete.mincol; + col_amount = (undo_info.data.col_adjust_delete.endcol + - undo_info.data.col_adjust_delete.mincol) + 1; + extmark_col_adjust(curbuf, + undo_info.data.col_adjust_delete.lnum, + mincol, + 0, + col_amount, + kExtmarkNoUndo); + // Redo + } else { + extmark_col_adjust_delete(curbuf, + undo_info.data.col_adjust_delete.lnum, + undo_info.data.col_adjust_delete.mincol, + undo_info.data.col_adjust_delete.endcol, + kExtmarkNoUndo, + undo_info.data.col_adjust_delete.eol); + } + // use extmark_adjust + } else if (undo_info.type == kLineAdjust) { + if (undo) { + // Undo - call signature type one - insert now + if (undo_info.data.adjust.amount == MAXLNUM) { + line1 = undo_info.data.adjust.line1; + line2 = MAXLNUM; + amount = -undo_info.data.adjust.amount_after; + amount_after = 0; + // Undo - call singature type two - delete now + } else if (undo_info.data.adjust.line2 == MAXLNUM) { + line1 = undo_info.data.adjust.line1; + line2 = undo_info.data.adjust.line2; + amount = -undo_info.data.adjust.amount; + amount_after = undo_info.data.adjust.amount_after; + // Undo - call signature three - move lines + } else { + line1 = (undo_info.data.adjust.line1 + + undo_info.data.adjust.amount); + line2 = (undo_info.data.adjust.line2 + + undo_info.data.adjust.amount); + amount = -undo_info.data.adjust.amount; + amount_after = -undo_info.data.adjust.amount_after; + } + // redo + } else { + line1 = undo_info.data.adjust.line1; + line2 = undo_info.data.adjust.line2; + amount = undo_info.data.adjust.amount; + amount_after = undo_info.data.adjust.amount_after; + } + extmark_adjust(curbuf, + line1, line2, amount, amount_after, kExtmarkNoUndo, false); + // kExtmarkCopy + } else if (undo_info.type == kExtmarkCopy) { + // Redo should be handled by kColAdjustDelete or kExtmarkCopyPlace + if (undo) { + extmark_set(curbuf, + undo_info.data.copy.ns_id, + undo_info.data.copy.mark_id, + undo_info.data.copy.lnum, + undo_info.data.copy.col, + kExtmarkNoUndo); + } + // uses extmark_copy_and_place + } else if (undo_info.type == kExtmarkCopyPlace) { + // Redo, undo is handle by kExtmarkCopy + if (!undo) { + extmark_copy_and_place(curbuf, + undo_info.data.copy_place.l_lnum, + undo_info.data.copy_place.l_col, + undo_info.data.copy_place.u_lnum, + undo_info.data.copy_place.u_col, + undo_info.data.copy_place.p_lnum, + undo_info.data.copy_place.p_col, + kExtmarkNoUndo, true, NULL); + } + // kExtmarkClear + } else if (undo_info.type == kExtmarkClear) { + // Redo, undo is handle by kExtmarkCopy + if (!undo) { + extmark_clear(curbuf, + undo_info.data.clear.ns_id, + undo_info.data.clear.l_lnum, + undo_info.data.clear.u_lnum, + kExtmarkNoUndo); + } + // kAdjustMove + } else if (undo_info.type == kAdjustMove) { + apply_undo_move(undo_info, undo); + // extmark_set + } else if (undo_info.type == kExtmarkSet) { + if (undo) { + extmark_del(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + kExtmarkNoUndo); + // Redo + } else { + extmark_set(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + undo_info.data.set.lnum, + undo_info.data.set.col, + kExtmarkNoUndo); + } + // extmark_set into update + } else if (undo_info.type == kExtmarkUpdate) { + if (undo) { + extmark_set(curbuf, + undo_info.data.update.ns_id, + undo_info.data.update.mark_id, + undo_info.data.update.old_lnum, + undo_info.data.update.old_col, + kExtmarkNoUndo); + // Redo + } else { + extmark_set(curbuf, + undo_info.data.update.ns_id, + undo_info.data.update.mark_id, + undo_info.data.update.lnum, + undo_info.data.update.col, + kExtmarkNoUndo); + } + // extmark_del + } else if (undo_info.type == kExtmarkDel) { + if (undo) { + extmark_set(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + undo_info.data.set.lnum, + undo_info.data.set.col, + kExtmarkNoUndo); + // Redo + } else { + extmark_del(curbuf, + undo_info.data.set.ns_id, + undo_info.data.set.mark_id, + kExtmarkNoUndo); + } + } +} + +// undo/redo an kExtmarkMove operation +static void apply_undo_move(ExtmarkUndoObject undo_info, bool undo) +{ + // 3 calls are required , see comment in function do_move (ex_cmds.c) + linenr_T line1 = undo_info.data.move.line1; + linenr_T line2 = undo_info.data.move.line2; + linenr_T last_line = undo_info.data.move.last_line; + linenr_T dest = undo_info.data.move.dest; + linenr_T num_lines = undo_info.data.move.num_lines; + linenr_T extra = undo_info.data.move.extra; + + if (undo) { + if (dest >= line2) { + extmark_adjust(curbuf, dest - num_lines + 1, dest, + last_line - dest + num_lines - 1, 0L, kExtmarkNoUndo, + true); + extmark_adjust(curbuf, dest - line2, dest - line1, + dest - line2, 0L, kExtmarkNoUndo, false); + } else { + extmark_adjust(curbuf, line1-num_lines, line2-num_lines, + last_line - (line1-num_lines), 0L, kExtmarkNoUndo, true); + extmark_adjust(curbuf, (line1-num_lines) + 1, (line2-num_lines) + 1, + -num_lines, 0L, kExtmarkNoUndo, false); + } + extmark_adjust(curbuf, last_line, last_line + num_lines - 1, + line1 - last_line, 0L, kExtmarkNoUndo, true); + // redo + } else { + extmark_adjust(curbuf, line1, line2, + last_line - line2, 0L, kExtmarkNoUndo, true); + if (dest >= line2) { + extmark_adjust(curbuf, line2 + 1, dest, + -num_lines, 0L, kExtmarkNoUndo, false); + } else { + extmark_adjust(curbuf, dest + 1, line1 - 1, + num_lines, 0L, kExtmarkNoUndo, false); + } + extmark_adjust(curbuf, last_line - num_lines + 1, last_line, + -(last_line - dest - extra), 0L, kExtmarkNoUndo, true); + } +} + + +/// Get the column position for EOL on a line +/// +/// If the lnum doesn't exist, returns 0 +colnr_T extmark_eol_col(buf_T *buf, linenr_T lnum) +{ + if (lnum > buf->b_ml.ml_line_count) { + return 0; + } + return (colnr_T)STRLEN(ml_get_buf(buf, lnum, false)) + 1; +} + + +// Adjust columns and rows for extmarks +// based off mark_col_adjust in mark.c +// returns true if something was moved otherwise false +static bool extmark_col_adjust_impl(buf_T *buf, linenr_T lnum, + colnr_T mincol, long lnum_amount, + bool for_delete, + long update_col) +{ + bool marks_exist = false; + + ExtmarkLine *extmarkline = extmarkline_ref(buf, lnum, false); + if (!extmarkline) { + return false; + } + + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, mincol, MAXCOL, { + marks_exist = true; + + // Calculate desired col amount where the adjustment should take place + // (not taking) eol into account + long col_amount; + if (for_delete) { + if (extmark->col < update_col) { + // When mark inside range + colnr_T start_effected_range = mincol - 1; + col_amount = -(extmark->col - start_effected_range); + } else { + // Mark outside of range + // -1 because a delete of width 0 should still move marks + col_amount = -(update_col - mincol) - 1; + } + } else { + // for anything other than deletes + col_amount = update_col; + } + + // No update required for this guy + if (col_amount == 0 && lnum_amount == 0) { + continue; + } + + // Set mark to start of line + if (col_amount < 0 + && extmark->col <= (colnr_T)-col_amount) { + extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, + extmarkline->lnum + lnum_amount, + 1, kExtmarkNoUndo, &mitr); + // Update the mark + } else { + // Note: The undo is handled by u_extmark_col_adjust, NoUndo here + extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, + extmarkline->lnum + lnum_amount, + extmark->col + (colnr_T)col_amount, kExtmarkNoUndo, &mitr); + } + }) + + if (kb_size(&extmarkline->items) == 0) { + kb_del(extmarklines, &buf->b_extlines, extmarkline); + extmarkline_free(extmarkline); + } + + return marks_exist; +} + +// Adjust columns and rows for extmarks +// +// based off mark_col_adjust in mark.c +// use extmark_col_adjust_impl to move columns by inserting +// Doesn't take the eol into consideration (possible to put marks in invalid +// positions) +void extmark_col_adjust(buf_T *buf, linenr_T lnum, + colnr_T mincol, long lnum_amount, + long col_amount, ExtmarkOp undo) +{ + assert(col_amount > INT_MIN && col_amount <= INT_MAX); + + bool marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, lnum_amount, + false, col_amount); + + if (undo == kExtmarkUndo && marks_moved) { + u_extmark_col_adjust(buf, lnum, mincol, lnum_amount, col_amount); + } +} + +// Adjust marks after a delete on a line +// +// Automatically readjusts to take the eol into account +// TODO(timeyyy): change mincol to be for the mark to be copied, not moved +// +// @param mincol First column that needs to be moved (start of delete range) + 1 +// @param endcol Last column which needs to be copied (end of delete range + 1) +void extmark_col_adjust_delete(buf_T *buf, linenr_T lnum, + colnr_T mincol, colnr_T endcol, + ExtmarkOp undo, int _eol) +{ + colnr_T start_effected_range = mincol; + + bool marks_moved; + if (undo == kExtmarkUndo) { + // Copy marks that would be effected by delete + // -1 because we need to restore if a mark existed at the start pos + u_extmark_copy(buf, 0, lnum, start_effected_range, lnum, endcol); + } + + marks_moved = extmark_col_adjust_impl(buf, lnum, mincol, 0, + true, (long)endcol); + + // Deletes at the end of the line have different behaviour than the normal + // case when deleted. + // Cleanup any marks that are floating beyond the end of line. + // we allow this to be passed in as well because the buffer may have already + // been mutated. + int eol = _eol; + if (!eol) { + eol = extmark_eol_col(buf, lnum); + } + FOR_ALL_EXTMARKS(buf, 1, lnum, eol, lnum, -1, { + extmark_update(extmark, buf, extmark->ns_id, extmark->mark_id, + extmarkline->lnum, (colnr_T)eol, kExtmarkNoUndo, &mitr); + }) + + // Record the undo for the actual move + if (marks_moved && undo == kExtmarkUndo) { + u_extmark_col_adjust_delete(buf, lnum, mincol, endcol, eol); + } +} + +// Adjust extmark row for inserted/deleted rows (columns stay fixed). +void extmark_adjust(buf_T *buf, + linenr_T line1, + linenr_T line2, + long amount, + long amount_after, + ExtmarkOp undo, + bool end_temp) +{ + ExtmarkLine *_extline; + + // btree needs to be kept ordered to work, so far only :move requires this + // 2nd call with end_temp = true unpack the lines from the temp position + if (end_temp && amount < 0) { + for (size_t i = 0; i < kv_size(buf->b_extmark_move_space); i++) { + _extline = kv_A(buf->b_extmark_move_space, i); + _extline->lnum += amount; + kb_put(extmarklines, &buf->b_extlines, _extline); + } + kv_size(buf->b_extmark_move_space) = 0; + return; + } + + bool marks_exist = false; + linenr_T *lp; + + linenr_T adj_start = line1; + if (amount == MAXLNUM) { + // Careful! marks from deleted region can end up on en extisting extmarkline + // that is goinig to be adjusted to the target position. + linenr_T join_num = line1 - amount_after; + ExtmarkLine *joinline = (join_num > line2 + ? extmarkline_ref(buf, join_num, false) : NULL); + + // extmark_adjust is already redoable, the copy should only be for undo + marks_exist = extmark_copy_and_place(curbuf, + line1, 1, + line2, MAXCOL, + line1, 1, + kExtmarkUndoNoRedo, true, joinline); + adj_start = line2+1; + } + FOR_ALL_EXTMARKLINES(buf, adj_start, MAXLNUM, { + marks_exist = true; + lp = &(extmarkline->lnum); + if (*lp <= line2) { + // 1st call with end_temp = true, store the lines in a temp position + if (end_temp && amount > 0) { + kb_del_itr_extmarklines(&buf->b_extlines, &itr); + kv_push(buf->b_extmark_move_space, extmarkline); + } + + *lp += amount; + } else if (amount_after && *lp > line2) { + *lp += amount_after; + } + }) + + if (undo == kExtmarkUndo && marks_exist) { + u_extmark_adjust(buf, line1, line2, amount, amount_after); + } +} + +/// Range points to copy +/// +/// if part of a larger iteration we can't delete, then the caller +/// must check for empty lines. +bool extmark_copy_and_place(buf_T *buf, + linenr_T l_lnum, colnr_T l_col, + linenr_T u_lnum, colnr_T u_col, + linenr_T p_lnum, colnr_T p_col, + ExtmarkOp undo, bool delete, + ExtmarkLine *destline) + +{ + bool marks_moved = false; + if (undo == kExtmarkUndo || undo == kExtmarkUndoNoRedo) { + // Copy marks that would be effected by delete + u_extmark_copy(buf, 0, l_lnum, l_col, u_lnum, u_col); + } + + // Move extmarks to their final position + // Careful: if we move items within the same line, we might change order of + // marks within the same extmarkline. Too keep it simple, first delete all + // items from the extmarkline and put them back in the right order. + FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { + kvec_t(Extmark) temp_space = KV_INITIAL_VALUE; + bool same_line = extmarkline == destline; + FOR_ALL_EXTMARKS_IN_LINE(extmarkline->items, + (extmarkline->lnum > l_lnum) ? 0 : l_col, + (extmarkline->lnum < u_lnum) ? MAXCOL : u_col, { + if (!destline) { + destline = extmarkline_ref(buf, p_lnum, true); + same_line = extmarkline == destline; + } + marks_moved = true; + if (!same_line) { + extmark_put(p_col, extmark->mark_id, destline, extmark->ns_id); + ExtmarkNs *ns_obj = pmap_get(uint64_t)(buf->b_extmark_ns, + extmark->ns_id); + pmap_put(uint64_t)(ns_obj->map, extmark->mark_id, destline); + } else { + kv_push(temp_space, *extmark); + } + // Delete old mark + kb_del_itr(markitems, &extmarkline->items, &mitr); + }) + if (same_line) { + for (size_t i = 0; i < kv_size(temp_space); i++) { + Extmark mark = kv_A(temp_space, i); + extmark_put(p_col, mark.mark_id, extmarkline, mark.ns_id); + } + kv_destroy(temp_space); + } else if (delete && kb_size(&extmarkline->items) == 0) { + kb_del_itr(extmarklines, &buf->b_extlines, &itr); + extmarkline_free(extmarkline); + } + }) + + // Record the undo for the actual move + if (marks_moved && undo == kExtmarkUndo) { + u_extmark_copy_place(buf, l_lnum, l_col, u_lnum, u_col, p_lnum, p_col); + } + + return marks_moved; +} + +// Get reference to line in kbtree_t, allocating it if neccessary. +ExtmarkLine *extmarkline_ref(buf_T *buf, linenr_T lnum, bool put) +{ + kbtree_t(extmarklines) *b = &buf->b_extlines; + ExtmarkLine t, **pp; + t.lnum = lnum; + + pp = kb_get(extmarklines, b, &t); + if (!pp) { + if (!put) { + return NULL; + } + ExtmarkLine *p = xcalloc(sizeof(ExtmarkLine), 1); + p->lnum = lnum; + // p->items zero initialized + kb_put(extmarklines, b, p); + return p; + } + // Return existing + return *pp; +} + +void extmarkline_free(ExtmarkLine *extmarkline) +{ + kb_destroy(markitems, (&extmarkline->items)); + xfree(extmarkline); +} + +/// Put an extmark into a line, +/// +/// caller must ensure combination of id and ns_id isn't in use. +void extmark_put(colnr_T col, uint64_t id, + ExtmarkLine *extmarkline, uint64_t ns) +{ + Extmark t; + t.col = col; + t.mark_id = id; + t.line = extmarkline; + t.ns_id = ns; + + kbtree_t(markitems) *b = &(extmarkline->items); + // kb_put requries the key to not be there + assert(!kb_getp(markitems, b, &t)); + + kb_put(markitems, b, t); +} + + diff --git a/src/nvim/mark_extended.h b/src/nvim/mark_extended.h new file mode 100644 index 0000000000..ee1da26875 --- /dev/null +++ b/src/nvim/mark_extended.h @@ -0,0 +1,282 @@ +#ifndef NVIM_MARK_EXTENDED_H +#define NVIM_MARK_EXTENDED_H + +#include "nvim/mark_extended_defs.h" +#include "nvim/buffer_defs.h" // for buf_T + + +// Macro Documentation: FOR_ALL_? +// Search exclusively using the range values given. +// Use MAXCOL/MAXLNUM for the start and end of the line/col. +// The ns parameter: Unless otherwise stated, this is only a starting point +// for the btree to searched in, the results being itterated over will +// still contain extmarks from other namespaces. + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, code)\ + kbitr_t(extmarklines) itr;\ + ExtmarkLine t;\ + t.lnum = l_lnum;\ + if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ + kb_itr_next(extmarklines, &buf->b_extlines, &itr);\ + }\ + ExtmarkLine *extmarkline;\ + for (; kb_itr_valid(&itr); kb_itr_next(extmarklines, \ + &buf->b_extlines, &itr)) { \ + extmarkline = kb_itr_key(&itr);\ + if (extmarkline->lnum > u_lnum) { \ + break;\ + }\ + code;\ + } + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, code)\ + kbitr_t(extmarklines) itr;\ + ExtmarkLine t;\ + t.lnum = u_lnum;\ + if (!kb_itr_get(extmarklines, &buf->b_extlines, &t, &itr)) { \ + kb_itr_prev(extmarklines, &buf->b_extlines, &itr);\ + }\ + ExtmarkLine *extmarkline;\ + for (; kb_itr_valid(&itr); kb_itr_prev(extmarklines, \ + &buf->b_extlines, &itr)) { \ + extmarkline = kb_itr_key(&itr);\ + if (extmarkline->lnum < l_lnum) { \ + break;\ + }\ + code;\ + } + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKS(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ + kbitr_t(markitems) mitr;\ + Extmark mt;\ + mt.ns_id = ns;\ + mt.mark_id = 0;\ + mt.line = NULL;\ + FOR_ALL_EXTMARKLINES(buf, l_lnum, u_lnum, { \ + mt.col = (extmarkline->lnum != l_lnum) ? MINCOL : l_col;\ + if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ + kb_itr_next(markitems, &extmarkline->items, &mitr);\ + } \ + Extmark *extmark;\ + for (; \ + kb_itr_valid(&mitr); \ + kb_itr_next(markitems, &extmarkline->items, &mitr)) { \ + extmark = &kb_itr_key(&mitr);\ + if (extmark->line->lnum == u_lnum \ + && extmark->col > u_col) { \ + break;\ + }\ + code;\ + }\ + }) + + +// see FOR_ALL_? for documentation +#define FOR_ALL_EXTMARKS_PREV(buf, ns, l_lnum, l_col, u_lnum, u_col, code)\ + kbitr_t(markitems) mitr;\ + Extmark mt;\ + mt.mark_id = sizeof(uint64_t);\ + mt.ns_id = ns;\ + FOR_ALL_EXTMARKLINES_PREV(buf, l_lnum, u_lnum, { \ + mt.col = (extmarkline->lnum != u_lnum) ? MAXCOL : u_col;\ + if (!kb_itr_get(markitems, &extmarkline->items, mt, &mitr)) { \ + kb_itr_prev(markitems, &extmarkline->items, &mitr);\ + } \ + Extmark *extmark;\ + for (; \ + kb_itr_valid(&mitr); \ + kb_itr_prev(markitems, &extmarkline->items, &mitr)) { \ + extmark = &kb_itr_key(&mitr);\ + if (extmark->line->lnum == l_lnum \ + && extmark->col < l_col) { \ + break;\ + }\ + code;\ + }\ + }) + + +#define FOR_ALL_EXTMARKS_IN_LINE(items, l_col, u_col, code)\ + kbitr_t(markitems) mitr;\ + Extmark mt;\ + mt.ns_id = 0;\ + mt.mark_id = 0;\ + mt.line = NULL;\ + mt.col = l_col;\ + colnr_T extmarkline_u_col = u_col;\ + if (!kb_itr_get(markitems, &items, mt, &mitr)) { \ + kb_itr_next(markitems, &items, &mitr);\ + } \ + Extmark *extmark;\ + for (; kb_itr_valid(&mitr); kb_itr_next(markitems, &items, &mitr)) { \ + extmark = &kb_itr_key(&mitr);\ + if (extmark->col > extmarkline_u_col) { \ + break;\ + }\ + code;\ + } + + +typedef struct ExtmarkNs { // For namespacing extmarks + PMap(uint64_t) *map; // For fast lookup + uint64_t free_id; // For automatically assigning id's +} ExtmarkNs; + + +typedef kvec_t(Extmark *) ExtmarkArray; + + +// Undo/redo extmarks + +typedef enum { + kExtmarkNOOP, // Extmarks shouldn't be moved + kExtmarkUndo, // Operation should be reversable/undoable + kExtmarkNoUndo, // Operation should not be reversable + kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable +} ExtmarkOp; + + +// adjust line numbers only, corresponding to mark_adjust call +typedef struct { + linenr_T line1; + linenr_T line2; + long amount; + long amount_after; +} Adjust; + +// adjust columns after split/join line, like mark_col_adjust +typedef struct { + linenr_T lnum; + colnr_T mincol; + long col_amount; + long lnum_amount; +} ColAdjust; + +// delete the columns between mincol and endcol +typedef struct { + linenr_T lnum; + colnr_T mincol; + colnr_T endcol; + int eol; +} ColAdjustDelete; + +// adjust linenumbers after :move operation +typedef struct { + linenr_T line1; + linenr_T line2; + linenr_T last_line; + linenr_T dest; + linenr_T num_lines; + linenr_T extra; +} AdjustMove; + +// TODO(bfredl): reconsider if we really should track mark creation/updating +// itself, these are not really "edit" operation. +// extmark was created +typedef struct { + uint64_t ns_id; + uint64_t mark_id; + linenr_T lnum; + colnr_T col; +} ExtmarkSet; + +// extmark was updated +typedef struct { + uint64_t ns_id; + uint64_t mark_id; + linenr_T old_lnum; + colnr_T old_col; + linenr_T lnum; + colnr_T col; +} ExtmarkUpdate; + +// copied mark before deletion (as operation is destructive) +typedef struct { + uint64_t ns_id; + uint64_t mark_id; + linenr_T lnum; + colnr_T col; +} ExtmarkCopy; + +// also used as part of :move operation? probably can be simplified to one +// event. +typedef struct { + linenr_T l_lnum; + colnr_T l_col; + linenr_T u_lnum; + colnr_T u_col; + linenr_T p_lnum; + colnr_T p_col; +} ExtmarkCopyPlace; + +// extmark was cleared. +// TODO(bfredl): same reconsideration as for ExtmarkSet/ExtmarkUpdate +typedef struct { + uint64_t ns_id; + linenr_T l_lnum; + linenr_T u_lnum; +} ExtmarkClear; + + +typedef enum { + kLineAdjust, + kColAdjust, + kColAdjustDelete, + kAdjustMove, + kExtmarkSet, + kExtmarkDel, + kExtmarkUpdate, + kExtmarkCopy, + kExtmarkCopyPlace, + kExtmarkClear, +} UndoObjectType; + +// TODO(bfredl): reduce the number of undo action types +struct undo_object { + UndoObjectType type; + union { + Adjust adjust; + ColAdjust col_adjust; + ColAdjustDelete col_adjust_delete; + AdjustMove move; + ExtmarkSet set; + ExtmarkUpdate update; + ExtmarkCopy copy; + ExtmarkCopyPlace copy_place; + ExtmarkClear clear; + } data; +}; + + +// For doing move of extmarks in substitutions +typedef struct { + lpos_T startpos; + lpos_T endpos; + linenr_T lnum; + int sublen; +} ExtmarkSubSingle; + +// For doing move of extmarks in substitutions +typedef struct { + lpos_T startpos; + lpos_T endpos; + linenr_T lnum; + linenr_T newline_in_pat; + linenr_T newline_in_sub; + linenr_T lnum_added; + lpos_T cm_start; // start of the match + lpos_T cm_end; // end of the match + int eol; // end of the match +} ExtmarkSubMulti; + +typedef kvec_t(ExtmarkSubSingle) extmark_sub_single_vec_t; +typedef kvec_t(ExtmarkSubMulti) extmark_sub_multi_vec_t; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "mark_extended.h.generated.h" +#endif + +#endif // NVIM_MARK_EXTENDED_H diff --git a/src/nvim/mark_extended_defs.h b/src/nvim/mark_extended_defs.h new file mode 100644 index 0000000000..565c599d06 --- /dev/null +++ b/src/nvim/mark_extended_defs.h @@ -0,0 +1,54 @@ +#ifndef NVIM_MARK_EXTENDED_DEFS_H +#define NVIM_MARK_EXTENDED_DEFS_H + +#include "nvim/pos.h" // for colnr_T +#include "nvim/map.h" // for uint64_t +#include "nvim/lib/kbtree.h" +#include "nvim/lib/kvec.h" + +struct ExtmarkLine; + +typedef struct Extmark +{ + uint64_t ns_id; + uint64_t mark_id; + struct ExtmarkLine *line; + colnr_T col; +} Extmark; + + +// We only need to compare columns as rows are stored in a different tree. +// Marks are ordered by: position, namespace, mark_id +// This improves moving marks but slows down all other use cases (searches) +static inline int extmark_cmp(Extmark a, Extmark b) +{ + int cmp = kb_generic_cmp(a.col, b.col); + if (cmp != 0) { + return cmp; + } + cmp = kb_generic_cmp(a.ns_id, b.ns_id); + if (cmp != 0) { + return cmp; + } + return kb_generic_cmp(a.mark_id, b.mark_id); +} + + +#define markitems_cmp(a, b) (extmark_cmp((a), (b))) +KBTREE_INIT(markitems, Extmark, markitems_cmp, 10) + +typedef struct ExtmarkLine +{ + linenr_T lnum; + kbtree_t(markitems) items; +} ExtmarkLine; + +#define EXTMARKLINE_CMP(a, b) (kb_generic_cmp((a)->lnum, (b)->lnum)) +KBTREE_INIT(extmarklines, ExtmarkLine *, EXTMARKLINE_CMP, 10) + + +typedef struct undo_object ExtmarkUndoObject; +typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; + + +#endif // NVIM_MARK_EXTENDED_DEFS_H diff --git a/src/nvim/memline.c b/src/nvim/memline.c index b85c23e50f..2824d57f49 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1437,7 +1437,7 @@ recover_names ( * Append the full path to name with path separators made into percent * signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") */ -static char *make_percent_swname(const char *dir, char *name) +char *make_percent_swname(const char *dir, char *name) FUNC_ATTR_NONNULL_ARG(1) { char *d = NULL; @@ -1929,6 +1929,7 @@ int ml_append_buf( colnr_T len, // length of new line, including NUL, or 0 bool newfile // flag, see above ) + FUNC_ATTR_NONNULL_ARG(1) { if (buf->b_ml.ml_mfp == NULL) return FAIL; diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 64aae71433..9bc6b23ce3 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -693,6 +693,8 @@ void free_all_mem(void) clear_hl_tables(false); list_free_log(); + + check_quickfix_busy(); } #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 1db8a1fa11..a871d424c6 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -30,7 +30,6 @@ #include "nvim/indent_c.h" #include "nvim/buffer_updates.h" #include "nvim/main.h" -#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -792,6 +791,8 @@ int prompt_for_number(int *mouse_used) cmdline_row = msg_row - 1; } need_wait_return = false; + msg_didany = false; + msg_didout = false; } else { cmdline_row = save_cmdline_row; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f6222f9d3f..2ef2c3101f 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3794,7 +3794,7 @@ find_decl ( valid = false; (void)valid; // Avoid "dead assignment" warning. t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD, - pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL, NULL); + pat, 1L, searchflags, RE_LAST, NULL); if (curwin->w_cursor.lnum >= old_pos.lnum) { t = false; // match after start is failure too } @@ -4936,7 +4936,8 @@ static void nv_ident(cmdarg_T *cap) /* put pattern in search history */ init_history(); add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); - (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0); + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0, + NULL); } else { g_tag_at_cursor = true; do_cmdline_cmd(buf); @@ -5363,7 +5364,7 @@ static void nv_search(cmdarg_T *cap) (void)normal_search(cap, cap->cmdchar, cap->searchbuf, (cap->arg || !equalpos(save_cursor, curwin->w_cursor)) - ? 0 : SEARCH_MARK); + ? 0 : SEARCH_MARK, NULL); } /* @@ -5373,14 +5374,15 @@ static void nv_search(cmdarg_T *cap) static void nv_next(cmdarg_T *cap) { pos_T old = curwin->w_cursor; - int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg); + int wrapped = false; + int i = normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, &wrapped); - if (i == 1 && equalpos(old, curwin->w_cursor)) { + if (i == 1 && !wrapped && equalpos(old, curwin->w_cursor)) { // Avoid getting stuck on the current cursor position, which can happen when // an offset is given and the cursor is on the last char in the buffer: // Repeat with count + 1. cap->count1 += 1; - (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg); + (void)normal_search(cap, 0, NULL, SEARCH_MARK | cap->arg, NULL); cap->count1 -= 1; } } @@ -5394,18 +5396,24 @@ static int normal_search( cmdarg_T *cap, int dir, char_u *pat, - int opt /* extra flags for do_search() */ + int opt, // extra flags for do_search() + int *wrapped ) { int i; + searchit_arg_T sia; cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; cap->oap->use_reg_one = true; curwin->w_set_curswant = true; + memset(&sia, 0, sizeof(sia)); i = do_search(cap->oap, dir, pat, cap->count1, - opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, NULL, NULL); + opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); + if (wrapped != NULL) { + *wrapped = sia.sa_wrapped; + } if (i == 0) { clearop(cap->oap); } else { @@ -6741,6 +6749,22 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; + case 'M': + { + const char_u *const ptr = get_cursor_line_ptr(); + + oap->motion_type = kMTCharWise; + oap->inclusive = false; + i = (int)mb_string2cells_len(ptr, STRLEN(ptr)); + if (cap->count0 > 0 && cap->count0 <= 100) { + coladvance((colnr_T)(i * cap->count0 / 100)); + } else { + coladvance((colnr_T)(i / 2)); + } + curwin->w_set_curswant = true; + } + break; + case '_': /* "g_": to the last non-blank character in the line or <count> lines * downward. */ diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 030782cbcc..2301b2159f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -49,6 +49,7 @@ #include "nvim/undo.h" #include "nvim/macros.h" #include "nvim/window.h" +#include "nvim/lib/kvec.h" #include "nvim/os/input.h" #include "nvim/os/time.h" @@ -306,6 +307,15 @@ void shift_line( change_indent(INDENT_SET, count, false, NUL, call_changed_bytes); } else { (void)set_indent(count, call_changed_bytes ? SIN_CHANGED : 0); + + colnr_T mincol = (curwin->w_cursor.col + 1) -p_sw; + colnr_T col_amount = left ? -p_sw : p_sw; + extmark_col_adjust(curbuf, + curwin->w_cursor.lnum, + mincol, + 0, + col_amount, + kExtmarkUndo); } } @@ -479,6 +489,10 @@ static void shift_block(oparg_T *oap, int amount) State = oldstate; curwin->w_cursor.col = oldcol; p_ri = old_p_ri; + + colnr_T col_amount = left ? -p_sw : p_sw; + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + curwin->w_cursor.col, 0, col_amount, kExtmarkUndo); } /* @@ -623,10 +637,19 @@ void op_reindent(oparg_T *oap, Indenter how) amount = how(); /* get the indent for this line */ if (amount >= 0 && set_indent(amount, SIN_UNDO)) { - /* did change the indent, call changed_lines() later */ - if (first_changed == 0) + // did change the indent, call changed_lines() later + if (first_changed == 0) { first_changed = curwin->w_cursor.lnum; + } last_changed = curwin->w_cursor.lnum; + + // Adjust extmarks + extmark_col_adjust(curbuf, + curwin->w_cursor.lnum, + 0, // mincol + 0, // lnum_amount + amount, // col_amount + kExtmarkUndo); } } ++curwin->w_cursor.lnum; @@ -1562,6 +1585,7 @@ int op_delete(oparg_T *oap) oap->end = curwin->w_cursor; curwin->w_cursor = oap->start; } + mb_adjust_opend(oap); } if (oap->line_count == 1) { /* delete characters within one line */ @@ -1620,6 +1644,8 @@ int op_delete(oparg_T *oap) curwin->w_cursor.col = 0; (void)del_bytes((colnr_T)n, !virtual_op, oap->op_type == OP_DELETE && !oap->is_VIsual); + extmark_col_adjust(curbuf, curwin->w_cursor.lnum, + (colnr_T)0, 0L, (long)-n, kExtmarkUndo); curwin->w_cursor = curpos; // restore curwin->w_cursor (void)do_join(2, false, false, false, false); } @@ -1631,10 +1657,36 @@ setmarks: if (oap->motion_type == kMTBlockWise) { curbuf->b_op_end.lnum = oap->end.lnum; curbuf->b_op_end.col = oap->start.col; - } else + } else { curbuf->b_op_end = oap->start; + } curbuf->b_op_start = oap->start; + // TODO(timeyyy): refactor: Move extended marks + // + 1 to change to buf mode, + // and + 1 because we only move marks after the deleted col + colnr_T mincol = oap->start.col + 1 + 1; + colnr_T endcol; + if (oap->motion_type == kMTBlockWise) { + // TODO(timeyyy): refactor extmark_col_adjust to take lnumstart, lnum_end ? + endcol = bd.end_vcol + 1; + for (lnum = curwin->w_cursor.lnum; lnum <= oap->end.lnum; lnum++) { + extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, + kExtmarkUndo, 0); + } + + // Delete characters within one line, + // The case with multiple lines is handled by do_join + } else if (oap->motion_type == kMTCharWise && oap->line_count == 1) { + // + 1 to change to buf mode, then plus 1 to fit function requirements + endcol = oap->end.col + 1 + 1; + + lnum = curwin->w_cursor.lnum; + if (oap->is_VIsual == false) { + endcol = MAX(endcol - 1, mincol); + } + extmark_col_adjust_delete(curbuf, lnum, mincol, endcol, kExtmarkUndo, 0); + } return OK; } @@ -2030,8 +2082,8 @@ bool swapchar(int op_type, pos_T *pos) pos_T sp = curwin->w_cursor; curwin->w_cursor = *pos; - /* don't use del_char(), it also removes composing chars */ - del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE); + // don't use del_char(), it also removes composing chars + del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false); ins_char(nc); curwin->w_cursor = sp; } else { @@ -2104,8 +2156,9 @@ void op_insert(oparg_T *oap, long count1) * values in "bd". */ if (u_save_cursor() == FAIL) return; - for (i = 0; i < bd.endspaces; i++) + for (i = 0; i < bd.endspaces; i++) { ins_char(' '); + } bd.textlen += bd.endspaces; } } else { @@ -2223,6 +2276,10 @@ void op_insert(oparg_T *oap, long count1) xfree(ins_text); } } + colnr_T col = oap->start.col; + for (linenr_T lnum = oap->start.lnum; lnum <= oap->end.lnum; lnum++) { + extmark_col_adjust(curbuf, lnum, col, 0, 1, kExtmarkUndo); + } } /* @@ -2693,6 +2750,27 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) } +static void extmarks_do_put(int dir, + size_t totlen, + MotionType y_type, + linenr_T lnum, + colnr_T col) +{ + // adjust extmarks + colnr_T col_amount = (colnr_T)(dir == FORWARD ? totlen-1 : totlen); + // Move extmark with char put + if (y_type == kMTCharWise) { + extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); + // Move extmark with blockwise put + } else if (y_type == kMTBlockWise) { + for (lnum = curbuf->b_op_start.lnum; + lnum <= curbuf->b_op_end.lnum; + lnum++) { + extmark_col_adjust(curbuf, lnum, col, 0, col_amount, kExtmarkUndo); + } + } +} + /* * Put contents of register "regname" into the text. * Caller must check "regname" to be valid! @@ -2707,8 +2785,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) char_u *oldp; int yanklen; size_t totlen = 0; // init for gcc - linenr_T lnum; - colnr_T col; + linenr_T lnum = 0; + colnr_T col = 0; size_t i; // index in y_array[] MotionType y_type; size_t y_size; @@ -3285,11 +3363,11 @@ error: curbuf->b_op_start.lnum++; } // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. But still needed in diff mode. + // can't be marks there. if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { + < curbuf->b_ml.ml_line_count) { mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), - (linenr_T)MAXLNUM, nr_lines, 0L, false); + (linenr_T)MAXLNUM, nr_lines, 0L, false, kExtmarkUndo); } // note changed text for displaying and folding @@ -3351,6 +3429,8 @@ end: /* If the cursor is past the end of the line put it at the end. */ adjust_cursor_eol(); + + extmarks_do_put(dir, totlen, y_type, lnum, col); } /* @@ -3693,7 +3773,10 @@ int do_join(size_t count, if (insert_space && t > 0) { curr = skipwhite(curr); - if (*curr != ')' && currsize != 0 && endcurr1 != TAB + if (*curr != NUL + && *curr != ')' + && currsize != 0 + && endcurr1 != TAB && (!has_format_option(FO_MBYTE_JOIN) || (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100)) && (!has_format_option(FO_MBYTE_JOIN2) @@ -3744,6 +3827,7 @@ int do_join(size_t count, * column. This is not Vi compatible, but Vi deletes the marks, thus that * should not really be a problem. */ + for (t = (linenr_T)count - 1;; t--) { cend -= currsize; memmove(cend, curr, (size_t)currsize); @@ -3755,12 +3839,18 @@ int do_join(size_t count, // 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]); + linenr_T lnum = curwin->w_cursor.lnum + t; + colnr_T mincol = (colnr_T)0; + long lnum_amount = (linenr_T)-t; + long col_amount = (long)(cend - newp - spaces_removed); + + mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed, + kExtmarkUndo); - mark_col_adjust(curwin->w_cursor.lnum + t, (colnr_T)0, (linenr_T)-t, - (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]; @@ -3768,6 +3858,7 @@ int do_join(size_t count, curr = skipwhite(curr); currsize = (int)STRLEN(curr); } + ml_replace(curwin->w_cursor.lnum, newp, false); if (setmark) { @@ -4188,14 +4279,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, 0); + (long)-next_leader_len, 0, kExtmarkUndo); } 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, 0); + (colnr_T)0, 0L, (long)-indent, 0, kExtmarkUndo); } } curwin->w_cursor.lnum--; @@ -4538,7 +4629,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) { int col; - char_u *buf1; + char_u *buf1 = NULL; char_u buf2[NUMBUFLEN]; int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin static bool hexupper = false; // 0xABC @@ -4847,7 +4938,6 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) *ptr = NUL; STRCAT(buf1, buf2); ins_str(buf1); // insert the new number - xfree(buf1); endpos = curwin->w_cursor; if (curwin->w_cursor.col) { curwin->w_cursor.col--; @@ -4861,7 +4951,25 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) curbuf->b_op_end.col--; } + // if buf1 wasn't allocated, only a singe ASCII char was changed in-place. + if (did_change && buf1 != NULL) { + extmark_col_adjust_delete(curbuf, + pos->lnum, + startpos.col + 2, + endpos.col + 1 + length, + kExtmarkUndo, + 0); + long col_amount = (long)STRLEN(buf1); + extmark_col_adjust(curbuf, + pos->lnum, + startpos.col + 1, + 0, + col_amount, + kExtmarkUndo); + } + theend: + xfree(buf1); if (visual) { curwin->w_cursor = save_cursor; } else if (did_change) { diff --git a/src/nvim/options.lua b/src/nvim/options.lua index e96b3f8e02..d20174466d 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -612,7 +612,7 @@ return { alloced=true, redraw={'current_window'}, varname='p_dip', - defaults={if_true={vi="internal,filler"}} + defaults={if_true={vi="internal,filler,closeoff"}} }, { full_name='digraph', abbreviation='dg', diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c index bd5b9b4506..4f525bed9a 100644 --- a/src/nvim/os/tty.c +++ b/src/nvim/os/tty.c @@ -6,6 +6,7 @@ // #include "nvim/os/os.h" +#include "nvim/os/tty.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/tty.c.generated.h" diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim index eae27ef74d..650c6155e2 100644 --- a/src/nvim/po/check.vim +++ b/src/nvim/po/check.vim @@ -47,6 +47,17 @@ let wsv = winsaveview() let error = 0 while 1 + let lnum = line('.') + if getline(lnum) =~ 'msgid "Text;.*;"' + if getline(lnum + 1) !~ '^msgstr "\([^;]\+;\)\+"' + echomsg 'Mismatching ; in line ' . (lnum + 1) + echomsg 'Did you forget the trailing semicolon?' + if error == 0 + let error = lnum + 1 + endif + endif + endif + if getline(line('.') - 1) !~ "no-c-format" " go over the "msgid" and "msgid_plural" lines let prevfromline = 'foobar' diff --git a/src/nvim/pos.h b/src/nvim/pos.h index 47d253e083..8e86ea08c5 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -14,6 +14,10 @@ typedef int colnr_T; enum { MAXLNUM = 0x7fffffff }; /// Maximal column number, 31 bits enum { MAXCOL = 0x7fffffff }; +// Minimum line number +enum { MINLNUM = 1 }; +// minimum column number +enum { MINCOL = 1 }; /* * position in file or buffer diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index c5e8d4b490..ed57b28029 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -80,6 +80,15 @@ struct qfline_S { #define LISTCOUNT 10 #define INVALID_QFIDX (-1) +/// Quickfix list type. +typedef enum +{ + QFLT_QUICKFIX, ///< Quickfix list - global list + QFLT_LOCATION, ///< Location list - per window list + QFLT_INTERNAL ///< Internal - Temporary list used by + // getqflist()/getloclist() +} qfltype_T; + /// Quickfix/Location list definition /// /// Usually the list contains one or more entries. But an empty list can be @@ -87,6 +96,7 @@ struct qfline_S { /// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { unsigned qf_id; ///< Unique identifier for this list + qfltype_T qfl_type; qfline_T *qf_start; ///< pointer to the first error qfline_T *qf_last; ///< pointer to the last error qfline_T *qf_ptr; ///< pointer to the current error @@ -120,6 +130,7 @@ struct qf_info_S { int qf_listcount; /* current number of lists */ int qf_curlist; /* current error list */ qf_list_T qf_lists[LISTCOUNT]; + qfltype_T qfl_type; // type of list }; static qf_info_T ql_info; // global quickfix list @@ -154,6 +165,13 @@ struct efm_S { int conthere; /* %> used */ }; +/// List of location lists to be deleted. +/// Used to delay the deletion of locations lists by autocmds. +typedef struct qf_delq_S { + struct qf_delq_S *next; + qf_info_T *qi; +} qf_delq_T; + enum { QF_FAIL = 0, QF_OK = 1, @@ -163,6 +181,8 @@ enum { QF_MULTISCAN = 5, }; +/// State information used to parse lines and add entries to a quickfix/location +/// list. typedef struct { char_u *linebuf; size_t linelen; @@ -196,14 +216,19 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.c.generated.h" #endif -/* Quickfix window check helper macro */ + +static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); + +// Quickfix window check helper macro #define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) /* Location list window check helper macro */ #define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) // Quickfix and location list stack check helper macros -#define IS_QF_STACK(qi) (qi == &ql_info) -#define IS_LL_STACK(qi) (qi != &ql_info) +#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX) +#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION) +#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX) +#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION) // // Return location list for window 'wp' @@ -211,6 +236,14 @@ typedef struct { // #define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +// Macro to loop through all the items in a quickfix list +// Quickfix item index starts from 1, so i below starts at 1 +#define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ + for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && i <= qfl->qf_count && qfp != NULL; \ + i++, qfp = qfp->qf_next) + + // Looking up a buffer can be slow if there are many. Remember the last one // to make this a lot faster if there are multiple matches in the same file. static char_u *qf_last_bufname = NULL; @@ -218,6 +251,50 @@ static bufref_T qf_last_bufref = { NULL, 0, 0 }; static char *e_loc_list_changed = N_("E926: Current location list was changed"); +// Counter to prevent autocmds from freeing up location lists when they are +// still being used. +static int quickfix_busy = 0; +static qf_delq_T *qf_delq_head = NULL; + +/// Process the next line from a file/buffer/list/string and add it +/// to the quickfix list 'qfl'. +static int qf_init_process_nextline(qf_list_T *qfl, + efm_T *fmt_first, + qfstate_T *state, + qffields_T *fields) +{ + int status; + + // Get the next line from a file/buffer/list/string + status = qf_get_nextline(state); + if (status != QF_OK) { + return status; + } + + status = qf_parse_line(qfl, state->linebuf, state->linelen, + fmt_first, fields); + if (status != QF_OK) { + return status; + } + + return qf_add_entry(qfl, + qfl->qf_directory, + (*fields->namebuf || qfl->qf_directory != NULL) + ? fields->namebuf + : ((qfl->qf_currfile != NULL && fields->valid) + ? qfl->qf_currfile : (char_u *)NULL), + fields->module, + 0, + fields->errmsg, + fields->lnum, + fields->col, + fields->use_viscol, + fields->pattern, + fields->enr, + fields->type, + fields->valid); +} + /// Read the errorfile "efile" into memory, line by line, building the error /// list. Set the error list's title to qf_title. /// @@ -229,8 +306,9 @@ static char *e_loc_list_changed = N_("E926: Current location list was changed"); /// @params enc If non-NULL, encoding used to parse errors /// /// @returns -1 for error, number of errors for success. -int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, - char_u *qf_title, char_u *enc) +int qf_init(win_T *wp, const char_u *restrict efile, + char_u *restrict errorformat, int newlist, + const char_u *restrict qf_title, char_u *restrict enc) { qf_info_T *qi = &ql_info; @@ -264,93 +342,96 @@ static struct fmtpattern { 'o', ".\\+" } }; -// Convert an errorformat pattern to a regular expression pattern. -// See fmt_pat definition above for the list of supported patterns. -static char_u *fmtpat_to_regpat( - const char_u *efmp, - efm_T *fmt_ptr, +/// Convert an errorformat pattern to a regular expression pattern. +/// See fmt_pat definition above for the list of supported patterns. The +/// pattern specifier is supplied in "efmpat". The converted pattern is stored +/// in "regpat". Returns a pointer to the location after the pattern. +static char_u * efmpat_to_regpat( + const char_u *efmpat, + char_u *regpat, + efm_T *efminfo, int idx, int round, - char_u *ptr, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - if (fmt_ptr->addr[idx]) { + if (efminfo->addr[idx]) { // Each errorformat pattern can occur only once snprintf((char *)errmsg, errmsglen, - _("E372: Too many %%%c in format string"), *efmp); + _("E372: Too many %%%c in format string"), *efmpat); EMSG(errmsg); return NULL; } if ((idx && idx < 6 - && vim_strchr((char_u *)"DXOPQ", fmt_ptr->prefix) != NULL) + && vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL) || (idx == 6 - && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL)) { + && vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) { snprintf((char *)errmsg, errmsglen, - _("E373: Unexpected %%%c in format string"), *efmp); + _("E373: Unexpected %%%c in format string"), *efmpat); EMSG(errmsg); return NULL; } - fmt_ptr->addr[idx] = (char_u)++round; - *ptr++ = '\\'; - *ptr++ = '('; + efminfo->addr[idx] = (char_u)++round; + *regpat++ = '\\'; + *regpat++ = '('; #ifdef BACKSLASH_IN_FILENAME - if (*efmp == 'f') { + if (*efmpat == 'f') { // Also match "c:" in the file name, even when // checking for a colon next: "%f:". // "\%(\a:\)\=" - STRCPY(ptr, "\\%(\\a:\\)\\="); - ptr += 10; + STRCPY(regpat, "\\%(\\a:\\)\\="); + regpat += 10; } #endif - if (*efmp == 'f' && efmp[1] != NUL) { - if (efmp[1] != '\\' && efmp[1] != '%') { + if (*efmpat == 'f' && efmpat[1] != NUL) { + if (efmpat[1] != '\\' && efmpat[1] != '%') { // A file name may contain spaces, but this isn't // in "\f". For "%f:%l:%m" there may be a ":" in // the file name. Use ".\{-1,}x" instead (x is // the next character), the requirement that :999: // follows should work. - STRCPY(ptr, ".\\{-1,}"); - ptr += 7; + STRCPY(regpat, ".\\{-1,}"); + regpat += 7; } else { // File name followed by '\\' or '%': include as // many file name chars as possible. - STRCPY(ptr, "\\f\\+"); - ptr += 4; + STRCPY(regpat, "\\f\\+"); + regpat += 4; } } else { char_u *srcptr = (char_u *)fmt_pat[idx].pattern; - while ((*ptr = *srcptr++) != NUL) { - ptr++; + while ((*regpat = *srcptr++) != NUL) { + regpat++; } } - *ptr++ = '\\'; - *ptr++ = ')'; + *regpat++ = '\\'; + *regpat++ = ')'; - return ptr; + return regpat; } -// Convert a scanf like format in 'errorformat' to a regular expression. -static char_u *scanf_fmt_to_regpat( +/// Convert a scanf like format in 'errorformat' to a regular expression. +/// Returns a pointer to the location after the pattern. +static char_u * scanf_fmt_to_regpat( + const char_u **pefmp, const char_u *efm, int len, - const char_u **pefmp, - char_u *ptr, + char_u *regpat, char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { const char_u *efmp = *pefmp; - if (*++efmp == '[' || *efmp == '\\') { - if ((*ptr++ = *efmp) == '[') { // %*[^a-z0-9] etc. + if (*efmp == '[' || *efmp == '\\') { + if ((*regpat++ = *efmp) == '[') { // %*[^a-z0-9] etc. if (efmp[1] == '^') { - *ptr++ = *++efmp; + *regpat++ = *++efmp; } if (efmp < efm + len) { - *ptr++ = *++efmp; // could be ']' - while (efmp < efm + len && (*ptr++ = *++efmp) != ']') { + *regpat++ = *++efmp; // could be ']' + while (efmp < efm + len && (*regpat++ = *++efmp) != ']') { } if (efmp == efm + len) { EMSG(_("E374: Missing ] in format string")); @@ -358,10 +439,10 @@ static char_u *scanf_fmt_to_regpat( } } } else if (efmp < efm + len) { // %*\D, %*\s etc. - *ptr++ = *++efmp; + *regpat++ = *++efmp; } - *ptr++ = '\\'; - *ptr++ = '+'; + *regpat++ = '\\'; + *regpat++ = '+'; } else { // TODO(vim): scanf()-like: %*ud, %*3c, %*f, ... ? snprintf((char *)errmsg, errmsglen, @@ -372,31 +453,27 @@ static char_u *scanf_fmt_to_regpat( *pefmp = efmp; - return ptr; + return regpat; } -// Analyze/parse an errorformat prefix. -static int efm_analyze_prefix(const char_u **pefmp, efm_T *fmt_ptr, - char_u *errmsg, size_t errmsglen) +/// Analyze/parse an errorformat prefix. +static const char_u *efm_analyze_prefix(const char_u *efmp, efm_T *efminfo, + char_u *errmsg, size_t errmsglen) FUNC_ATTR_NONNULL_ALL { - const char_u *efmp = *pefmp; - if (vim_strchr((char_u *)"+-", *efmp) != NULL) { - fmt_ptr->flags = *efmp++; + efminfo->flags = *efmp++; } if (vim_strchr((char_u *)"DXAEWICZGOPQ", *efmp) != NULL) { - fmt_ptr->prefix = *efmp; + efminfo->prefix = *efmp; } else { snprintf((char *)errmsg, errmsglen, _("E376: Invalid %%%c in format string prefix"), *efmp); EMSG(errmsg); - return FAIL; + return NULL; } - *pefmp = efmp; - - return OK; + return efmp; } @@ -419,16 +496,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } } if (idx < FMT_PATTERNS) { - ptr = fmtpat_to_regpat(efmp, fmt_ptr, idx, round, ptr, - errmsg, errmsglen); + ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round, errmsg, + errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } round++; } else if (*efmp == '*') { - ptr = scanf_fmt_to_regpat(efm, len, &efmp, ptr, errmsg, errmsglen); + efmp++; + ptr = scanf_fmt_to_regpat(&efmp, efm, len, ptr, errmsg, errmsglen); if (ptr == NULL) { - return -1; + return FAIL; } } else if (vim_strchr((char_u *)"%\\.^$~[", *efmp) != NULL) { *ptr++ = *efmp; // regexp magic characters @@ -437,14 +515,17 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, } else if (*efmp == '>') { fmt_ptr->conthere = true; } else if (efmp == efm + 1) { // analyse prefix - if (efm_analyze_prefix(&efmp, fmt_ptr, errmsg, errmsglen) == FAIL) { - return -1; + // prefix is allowed only at the beginning of the errorformat + // option part + efmp = efm_analyze_prefix(efmp, fmt_ptr, errmsg, errmsglen); + if (efmp == NULL) { + return FAIL; } } else { snprintf((char *)errmsg, CMDBUFFSIZE + 1, _("E377: Invalid %%%c in format string"), *efmp); EMSG(errmsg); - return -1; + return FAIL; } } else { // copy normal character if (*efmp == '\\' && efmp + 1 < efm + len) { @@ -460,7 +541,7 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr, *ptr++ = '$'; *ptr = NUL; - return 0; + return OK; } static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls @@ -476,7 +557,42 @@ static void free_efm_list(efm_T **efm_first) fmt_start = NULL; } -// Parse 'errorformat' option +/// Compute the size of the buffer used to convert a 'errorformat' pattern into +/// a regular expression pattern. +static size_t efm_regpat_bufsz(char_u *efm) +{ + size_t sz; + + sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); + for (int i = FMT_PATTERNS - 1; i >= 0; ) { + sz += STRLEN(fmt_pat[i--].pattern); + } +#ifdef BACKSLASH_IN_FILENAME + sz += 12; // "%f" can become twelve chars longer (see efm_to_regpat) +#else + sz += 2; // "%f" can become two chars longer +#endif + + return sz; +} + +/// Return the length of a 'errorformat' option part (separated by ","). +static int efm_option_part_len(char_u *efm) +{ + int len; + + for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { + if (efm[len] == '\\' && efm[len + 1] != NUL) { + len++; + } + } + + return len; +} + +/// Parse the 'errorformat' option. Multiple parts in the 'errorformat' option +/// are parsed and converted to regular expressions. Returns information about +/// the parsed 'errorformat' option. static efm_T * parse_efm_option(char_u *efm) { efm_T *fmt_ptr = NULL; @@ -488,16 +604,8 @@ static efm_T * parse_efm_option(char_u *efm) char_u *errmsg = xmalloc(errmsglen); // Get some space to modify the format string into. - size_t i = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); - 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); + size_t sz = efm_regpat_bufsz(efm); + char_u *fmtstr = xmalloc(sz); while (efm[0] != NUL) { // Allocate a new eformat structure and put it at the end of the list @@ -510,13 +618,9 @@ static efm_T * parse_efm_option(char_u *efm) fmt_last = fmt_ptr; // Isolate one part in the 'errorformat' option - for (len = 0; efm[len] != NUL && efm[len] != ','; len++) { - if (efm[len] == '\\' && efm[len + 1] != NUL) { - len++; - } - } + len = efm_option_part_len(efm); - if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == -1) { + if (efm_to_regpat(efm, len, fmt_ptr, fmtstr, errmsg, errmsglen) == FAIL) { goto parse_efm_error; } if ((fmt_ptr->prog = vim_regcomp(fmtstr, RE_MAGIC + RE_STRING)) == NULL) { @@ -542,6 +646,7 @@ parse_efm_end: return fmt_first; } +/// Allocate more memory for the line buffer used for parsing lines. static char_u *qf_grow_linebuf(qfstate_T *state, size_t newsz) { // If the line exceeds LINE_MAXLEN exclude the last @@ -783,25 +888,41 @@ static int qf_get_nextline(qfstate_T *state) return QF_OK; } -// Returns true if the specified quickfix/location list is empty. -static bool qf_list_empty(const qf_info_T *qi, int qf_idx) +/// Returns true if the specified quickfix/location stack is empty +static bool qf_stack_empty(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - if (qi == NULL || qf_idx < 0 || qf_idx >= LISTCOUNT) { - return true; - } - return qi->qf_lists[qf_idx].qf_count <= 0; + return qi == NULL || qi->qf_listcount <= 0; +} + +/// Returns true if the specified quickfix/location list is empty. +static bool qf_list_empty(qf_list_T *qfl) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return qfl == NULL || qfl->qf_count <= 0; +} + +/// Returns true if the specified quickfix/location list is not empty and +/// has valid entries. +static bool qf_list_has_valid_entries(qf_list_T *qfl) +{ + return !qf_list_empty(qfl) && !qfl->qf_nonevalid; +} + +/// Return a pointer to a list in the specified quickfix stack +static qf_list_T * qf_get_list(qf_info_T *qi, int idx) +{ + return &qi->qf_lists[idx]; } /// Parse a line and get the quickfix fields. /// Return the QF_ status. -static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf, +static int qf_parse_line(qf_list_T *qfl, char_u *linebuf, size_t linelen, efm_T *fmt_first, qffields_T *fields) { efm_T *fmt_ptr; int idx = 0; char_u *tail = NULL; - qf_list_T *qfl = &qi->qf_lists[qf_idx]; int status; restofline: @@ -857,7 +978,7 @@ restofline: qfl->qf_multiignore = false; // reset continuation } 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); + status = qf_parse_multiline_pfx(idx, qfl, fields); if (status != QF_OK) { return status; } @@ -880,6 +1001,79 @@ restofline: return QF_OK; } +// Allocate the fields used for parsing lines and populating a quickfix list. +static void qf_alloc_fields(qffields_T *pfields) + FUNC_ATTR_NONNULL_ALL +{ + pfields->namebuf = xmalloc(CMDBUFFSIZE + 1); + pfields->module = xmalloc(CMDBUFFSIZE + 1); + pfields->errmsglen = CMDBUFFSIZE + 1; + pfields->errmsg = xmalloc(pfields->errmsglen); + pfields->pattern = xmalloc(CMDBUFFSIZE + 1); +} + +// Free the fields used for parsing lines and populating a quickfix list. +static void qf_free_fields(qffields_T *pfields) + FUNC_ATTR_NONNULL_ALL +{ + xfree(pfields->namebuf); + xfree(pfields->module); + xfree(pfields->errmsg); + xfree(pfields->pattern); +} + +// Setup the state information used for parsing lines and populating a +// quickfix list. +static int qf_setup_state( + qfstate_T *pstate, + char_u *restrict enc, + const char_u *restrict efile, + typval_T *tv, + buf_T *buf, + linenr_T lnumfirst, + linenr_T lnumlast) + FUNC_ATTR_NONNULL_ARG(1) +{ + pstate->vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) { + convert_setup(&pstate->vc, enc, p_enc); + } + + if (efile != NULL + && (pstate->fd = os_fopen((const char *)efile, "r")) == NULL) { + EMSG2(_(e_openerrf), efile); + return FAIL; + } + + if (tv != NULL) { + if (tv->v_type == VAR_STRING) { + pstate->p_str = tv->vval.v_string; + } else if (tv->v_type == VAR_LIST) { + pstate->p_li = tv_list_first(tv->vval.v_list); + } + pstate->tv = tv; + } + pstate->buf = buf; + pstate->buflnum = lnumfirst; + pstate->lnumlast = lnumlast; + + return OK; +} + +// Cleanup the state information used for parsing lines and populating a +// quickfix list. +static void qf_cleanup_state(qfstate_T *pstate) + FUNC_ATTR_NONNULL_ALL +{ + if (pstate->fd != NULL) { + fclose(pstate->fd); + } + xfree(pstate->growbuf); + if (pstate->vc.vc_type != CONV_NONE) { + convert_setup(&pstate->vc, NULL, NULL); + } +} + // Read the errorfile "efile" into memory, line by line, building the error // list. // Alternative: when "efile" is NULL read errors from buffer "buf". @@ -892,19 +1086,20 @@ static int qf_init_ext( qf_info_T *qi, int qf_idx, - char_u *efile, + const char_u *restrict efile, buf_T *buf, typval_T *tv, - char_u *errorformat, - int newlist, // TRUE: start a new error list + char_u *restrict errorformat, + bool newlist, // true: start a new error list linenr_T lnumfirst, // first line number to use linenr_T lnumlast, // last line number to use - char_u *qf_title, - char_u *enc + const char_u *restrict qf_title, + char_u *restrict enc ) + FUNC_ATTR_NONNULL_ARG(1) { - qfstate_T state; - qffields_T fields; + qfstate_T state = { 0 }; + qffields_T fields = { 0 }; qfline_T *old_last = NULL; bool adding = false; static efm_T *fmt_first = NULL; @@ -916,21 +1111,9 @@ qf_init_ext( // Do not used the cached buffer, it may have been wiped out. XFREE_CLEAR(qf_last_bufname); - memset(&state, 0, sizeof(state)); - memset(&fields, 0, sizeof(fields)); - state.vc.vc_type = CONV_NONE; - if (enc != NULL && *enc != NUL) { - convert_setup(&state.vc, enc, p_enc); - } - - fields.namebuf = xmalloc(CMDBUFFSIZE + 1); - fields.module = xmalloc(CMDBUFFSIZE + 1); - fields.errmsglen = CMDBUFFSIZE + 1; - fields.errmsg = xmalloc(fields.errmsglen); - fields.pattern = xmalloc(CMDBUFFSIZE + 1); - - if (efile != NULL && (state.fd = os_fopen((char *)efile, "r")) == NULL) { - EMSG2(_(e_openerrf), efile); + qf_alloc_fields(&fields); + if (qf_setup_state(&state, enc, efile, tv, buf, + lnumfirst, lnumlast) == FAIL) { goto qf_init_end; } @@ -941,12 +1124,12 @@ qf_init_ext( } else { // Adding to existing list, use last entry. adding = true; - if (qi->qf_lists[qf_idx].qf_count > 0) { + if (!qf_list_empty(qf_get_list(qi, qf_idx) )) { old_last = qi->qf_lists[qf_idx].qf_last; } } - qf_list_T *qfl = &qi->qf_lists[qf_idx]; + qf_list_T *qfl = qf_get_list(qi, qf_idx); // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { @@ -979,57 +1162,19 @@ qf_init_ext( */ got_int = FALSE; - if (tv != NULL) { - if (tv->v_type == VAR_STRING) { - state.p_str = tv->vval.v_string; - } else if (tv->v_type == VAR_LIST) { - state.p_list = tv->vval.v_list; - state.p_li = tv_list_first(tv->vval.v_list); - } - state.tv = tv; - } - state.buf = buf; - state.buflnum = lnumfirst; - state.lnumlast = lnumlast; - /* * Read the lines in the error file one by one. * Try to recognize one of the error formats in each line. */ while (!got_int) { - // Get the next line from a file/buffer/list/string - status = qf_get_nextline(&state); + status = qf_init_process_nextline(qfl, fmt_first, &state, &fields); if (status == QF_END_OF_INPUT) { // end of input break; } - - status = qf_parse_line(qi, qf_idx, state.linebuf, state.linelen, - fmt_first, &fields); if (status == QF_FAIL) { goto error2; } - if (status == QF_IGNORE_LINE) { - continue; - } - if (qf_add_entry(qi, - qf_idx, - qfl->qf_directory, - (*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, - fields.col, - fields.use_viscol, - fields.pattern, - fields.enr, - fields.type, - fields.valid) == FAIL) { - goto error2; - } line_breakcheck(); } if (state.fd == NULL || !ferror(state.fd)) { @@ -1052,45 +1197,34 @@ qf_init_ext( error2: if (!adding) { // Error when creating a new list. Free the new list - qf_free(qi, qi->qf_curlist); + qf_free(qfl); qi->qf_listcount--; if (qi->qf_curlist > 0) { qi->qf_curlist--; } } qf_init_end: - if (state.fd != NULL) { - fclose(state.fd); - } - xfree(fields.namebuf); - xfree(fields.module); - xfree(fields.errmsg); - xfree(fields.pattern); - xfree(state.growbuf); - if (qf_idx == qi->qf_curlist) { qf_update_buffer(qi, old_last); } - - if (state.vc.vc_type != CONV_NONE) { - convert_setup(&state.vc, NULL, NULL); - } + qf_cleanup_state(&state); + qf_free_fields(&fields); 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, const char_u *title) +static void qf_store_title(qf_list_T *qfl, const char_u *title) FUNC_ATTR_NONNULL_ARG(1) { - XFREE_CLEAR(qi->qf_lists[qf_idx].qf_title); + XFREE_CLEAR(qfl->qf_title); if (title != NULL) { size_t len = STRLEN(title) + 1; char_u *p = xmallocz(len); - qi->qf_lists[qf_idx].qf_title = p; + qfl->qf_title = p; xstrlcpy((char *)p, (const char *)title, len + 1); } } @@ -1108,35 +1242,256 @@ static char_u * qf_cmdtitle(char_u *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. -static void qf_new_list(qf_info_T *qi, char_u *qf_title) +/// Return a pointer to the current list in the specified quickfix stack +static qf_list_T * qf_get_curlist(qf_info_T *qi) +{ + return qf_get_list(qi, qi->qf_curlist); +} + +/// Prepare for adding a new quickfix list. If the current list is in the +/// middle of the stack, then all the following lists are freed and then +/// the new list is added. +static void qf_new_list(qf_info_T *qi, const char_u *qf_title) { int i; + qf_list_T *qfl; // If the current entry is not the last entry, delete entries beyond // the current entry. This makes it possible to browse in a tree-like // way with ":grep'. - while (qi->qf_listcount > qi->qf_curlist + 1) - qf_free(qi, --qi->qf_listcount); + while (qi->qf_listcount > qi->qf_curlist + 1) { + qf_free(&qi->qf_lists[--qi->qf_listcount]); + } /* * When the stack is full, remove to oldest entry * Otherwise, add a new entry. */ if (qi->qf_listcount == LISTCOUNT) { - qf_free(qi, 0); - for (i = 1; i < LISTCOUNT; ++i) + qf_free(&qi->qf_lists[0]); + for (i = 1; i < LISTCOUNT; i++) { qi->qf_lists[i - 1] = qi->qf_lists[i]; + } qi->qf_curlist = LISTCOUNT - 1; } else qi->qf_curlist = qi->qf_listcount++; - memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qi->qf_curlist, qf_title); - qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id; + qfl = qf_get_curlist(qi); + memset(qfl, 0, sizeof(qf_list_T)); + qf_store_title(qfl, qf_title); + qfl->qfl_type = qi->qfl_type; + qfl->qf_id = ++last_qf_id; +} + +/// Parse the match for filename ('%f') pattern in regmatch. +/// Return the matched value in "fields->namebuf". +static int qf_parse_fmt_f(regmatch_T *rmp, + int midx, + qffields_T *fields, + int prefix) +{ + char_u c; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + + // Expand ~/file and $HOME/file to full path. + c = *rmp->endp[midx]; + *rmp->endp[midx] = NUL; + expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); + *rmp->endp[midx] = c; + + // For separate filename patterns (%O, %P and %Q), the specified file + // should exist. + if (vim_strchr((char_u *)"OPQ", prefix) != NULL + && !os_path_exists(fields->namebuf)) { + return QF_FAIL; + } + + return QF_OK; +} + +/// Parse the match for error number ('%n') pattern in regmatch. +/// Return the matched value in "fields->enr". +static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->enr = (int)atol((char *)rmp->startp[midx]); + return QF_OK; +} + +/// Parse the match for line number (%l') pattern in regmatch. +/// Return the matched value in "fields->lnum". +static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->lnum = atol((char *)rmp->startp[midx]); + return QF_OK; +} + +/// Parse the match for column number ('%c') pattern in regmatch. +/// Return the matched value in "fields->col". +static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->col = (int)atol((char *)rmp->startp[midx]); + return QF_OK; +} + +/// Parse the match for error type ('%t') pattern in regmatch. +/// Return the matched value in "fields->type". +static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->type = *rmp->startp[midx]; + return QF_OK; +} + +/// Parse the match for '%+' format pattern. The whole matching line is included +/// in the error string. Return the matched line in "fields->errmsg". +static int qf_parse_fmt_plus(char_u *linebuf, + size_t linelen, + qffields_T *fields) +{ + if (linelen >= fields->errmsglen) { + // linelen + null terminator + fields->errmsg = xrealloc(fields->errmsg, linelen + 1); + fields->errmsglen = linelen + 1; + } + STRLCPY(fields->errmsg, linebuf, linelen + 1); + return QF_OK; +} + +/// Parse the match for error message ('%m') pattern in regmatch. +/// Return the matched value in "fields->errmsg". +static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len >= fields->errmsglen) { + // len + null terminator + fields->errmsg = xrealloc(fields->errmsg, len + 1); + fields->errmsglen = len + 1; + } + STRLCPY(fields->errmsg, rmp->startp[midx], len + 1); + return QF_OK; +} + +/// Parse the match for rest of a single-line file message ('%r') pattern. +/// Return the matched value in "tail". +static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char_u **tail) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + *tail = rmp->startp[midx]; + return QF_OK; +} + +/// Parse the match for the pointer line ('%p') pattern in regmatch. +/// Return the matched value in "fields->col". +static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) +{ + char_u *match_ptr; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + fields->col = 0; + for (match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx]; + match_ptr++) { + fields->col++; + if (*match_ptr == TAB) { + fields->col += 7; + fields->col -= fields->col % 8; + } + } + fields->col++; + fields->use_viscol = true; + return QF_OK; +} + +/// Parse the match for the virtual column number ('%v') pattern in regmatch. +/// Return the matched value in "fields->col". +static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) +{ + if (rmp->startp[midx] == NULL) { + return QF_FAIL; + } + fields->col = (int)atol((char *)rmp->startp[midx]); + fields->use_viscol = true; + return QF_OK; +} + +/// Parse the match for the search text ('%s') pattern in regmatch. +/// Return the matched value in "fields->pattern". +static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + if (len > CMDBUFFSIZE - 5) { + len = CMDBUFFSIZE - 5; + } + STRCPY(fields->pattern, "^\\V"); + xstrlcat((char *)fields->pattern, (char *)rmp->startp[midx], len + 4); + fields->pattern[len + 3] = '\\'; + fields->pattern[len + 4] = '$'; + fields->pattern[len + 5] = NUL; + return QF_OK; +} + +/// Parse the match for the module ('%o') pattern in regmatch. +/// Return the matched value in "fields->module". +static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) +{ + size_t len; + size_t dsize; + + if (rmp->startp[midx] == NULL || rmp->endp[midx] == NULL) { + return QF_FAIL; + } + len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); + dsize = STRLEN(fields->module) + len + 1; + if (dsize > CMDBUFFSIZE) { + dsize = CMDBUFFSIZE; + } + xstrlcat((char *)fields->module, (char *)rmp->startp[midx], dsize); + return QF_OK; } +/// 'errorformat' format pattern parser functions. +/// The '%f' and '%r' formats are parsed differently from other formats. +/// See qf_parse_match() for details. +static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = { + NULL, + qf_parse_fmt_n, + qf_parse_fmt_l, + qf_parse_fmt_c, + qf_parse_fmt_t, + qf_parse_fmt_m, + NULL, + qf_parse_fmt_p, + qf_parse_fmt_v, + qf_parse_fmt_s, + qf_parse_fmt_o +}; + /// 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, @@ -1147,7 +1502,8 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, { char_u idx = fmt_ptr->prefix; int i; - size_t len; + int midx; + int status; if ((idx == 'C' || idx == 'Z') && !qf_multiline) { return QF_FAIL; @@ -1161,118 +1517,26 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, // 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; + for (i = 0; i < FMT_PATTERNS; i++) { + status = QF_OK; + midx = (int)fmt_ptr->addr[i]; + if (i == 0 && midx > 0) { // %f + status = qf_parse_fmt_f(regmatch, midx, fields, idx); + } else if (i == 5) { + if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ + status = qf_parse_fmt_plus(linebuf, linelen, fields); + } else if (midx > 0) { // %m + status = qf_parse_fmt_m(regmatch, midx, fields); } + } else if (i == 6 && midx > 0) { // %r + status = qf_parse_fmt_r(regmatch, midx, tail); + } else if (midx > 0) { // others + status = (qf_parse_fmt[i])(regmatch, midx, fields); } - 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; + + if (status != QF_OK) { + return status; } - STRNCAT(fields->module, regmatch->startp[i], len); } return QF_OK; @@ -1384,8 +1648,7 @@ static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen, } /// 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) +static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) { if (!qfl->qf_multiignore) { qfline_T *qfprev = qfl->qf_last; @@ -1416,7 +1679,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, } qfprev->qf_viscol = fields->use_viscol; if (!qfprev->qf_fnum) { - qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory, + qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory, *fields->namebuf || qfl->qf_directory ? fields->namebuf : qfl->qf_currfile && fields->valid @@ -1431,7 +1694,18 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, return QF_IGNORE_LINE; } -/// Free a location list. +/// Queue location list stack delete request. +static void locstack_queue_delreq(qf_info_T *qi) +{ + qf_delq_T *q; + + q = xmalloc(sizeof(qf_delq_T)); + q->qi = qi; + q->next = qf_delq_head; + qf_delq_head = q; +} + +/// Free a location list stack static void ll_free_all(qf_info_T **pqi) { int i; @@ -1444,10 +1718,17 @@ static void ll_free_all(qf_info_T **pqi) qi->qf_refcount--; if (qi->qf_refcount < 1) { - /* No references to this location list */ - for (i = 0; i < qi->qf_listcount; ++i) - qf_free(qi, i); - xfree(qi); + // No references to this location list. + // If the location list is still in use, then queue the delete request + // to be processed later. + if (quickfix_busy > 0) { + locstack_queue_delreq(qi); + } else { + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); + } + xfree(qi); + } } } @@ -1461,16 +1742,61 @@ void qf_free_all(win_T *wp) /* location list */ ll_free_all(&wp->w_llist); ll_free_all(&wp->w_llist_ref); - } else - /* quickfix list */ - for (i = 0; i < qi->qf_listcount; ++i) - qf_free(qi, i); + } else { + // quickfix list + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); + } + } +} + +/// Delay freeing of location list stacks when the quickfix code is running. +/// Used to avoid problems with autocmds freeing location list stacks when the +/// quickfix code is still referencing the stack. +/// Must always call decr_quickfix_busy() exactly once after this. +static void incr_quickfix_busy(void) +{ + quickfix_busy++; +} + +/// Safe to free location list stacks. Process any delayed delete requests. +static void decr_quickfix_busy(void) +{ + quickfix_busy--; + if (quickfix_busy == 0) { + // No longer referencing the location lists. Process all the pending + // delete requests. + while (qf_delq_head != NULL) { + qf_delq_T *q = qf_delq_head; + + qf_delq_head = q->next; + ll_free_all(&q->qi); + xfree(q); + } + } +#ifdef ABORT_ON_INTERNAL_ERROR + if (quickfix_busy < 0) { + EMSG("quickfix_busy has become negative"); + abort(); + } +#endif +} + +#if defined(EXITFREE) +void check_quickfix_busy(void) +{ + if (quickfix_busy != 0) { + EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy); +# ifdef ABORT_ON_INTERNAL_ERROR + abort(); +# endif + } } +#endif /// Add an entry to the end of the list of errors. /// -/// @param qi quickfix list -/// @param qf_idx list index +/// @param qfl quickfix list entry /// @param dir optional directory name /// @param fname file name or NULL /// @param module module name or NULL @@ -1484,8 +1810,8 @@ void qf_free_all(win_T *wp) /// @param type type character /// @param valid valid entry /// -/// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, +/// @returns QF_OK or QF_FAIL. +static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, 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) @@ -1499,10 +1825,10 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_fnum = bufnum; if (buf != NULL) { buf->b_has_qf_entry |= - IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; } } else { - qfp->qf_fnum = qf_get_fnum(qi, qf_idx, dir, fname); + qfp->qf_fnum = qf_get_fnum(qfl, dir, fname); } qfp->qf_text = vim_strsave(mesg); qfp->qf_lnum = lnum; @@ -1525,12 +1851,12 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qf_idx].qf_last; - if (qi->qf_lists[qf_idx].qf_count == 0) { + lastp = &qfl->qf_last; + if (qf_list_empty(qfl)) { // first element in the list - qi->qf_lists[qf_idx].qf_start = qfp; - qi->qf_lists[qf_idx].qf_ptr = qfp; - qi->qf_lists[qf_idx].qf_index = 0; + qfl->qf_start = qfp; + qfl->qf_ptr = qfp; + qfl->qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1540,33 +1866,31 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qf_idx].qf_count++; - if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { + qfl->qf_count++; + if (qfl->qf_index == 0 && qfp->qf_valid) { // first valid entry - qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; - qi->qf_lists[qf_idx].qf_ptr = qfp; + qfl->qf_index = qfl->qf_count; + qfl->qf_ptr = qfp; } - return OK; + return QF_OK; } -/* - * Allocate a new location list - */ -static qf_info_T *ll_new_list(void) +/// Allocate a new quickfix/location list stack +static qf_info_T *qf_alloc_stack(qfltype_T qfltype) FUNC_ATTR_NONNULL_RET { qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; + qi->qfl_type = qfltype; return qi; } -/* - * Return the location list for window 'wp'. - * If not present, allocate a location list - */ +/// Return the location list stack for window 'wp'. +/// If not present, allocate a location list stack static qf_info_T *ll_get_or_alloc_list(win_T *wp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { if (IS_LL_WINDOW(wp)) /* For a location list window, use the referenced location list */ @@ -1578,127 +1902,176 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) */ ll_free_all(&wp->w_llist_ref); - if (wp->w_llist == NULL) - wp->w_llist = ll_new_list(); /* new location list */ + if (wp->w_llist == NULL) { + wp->w_llist = qf_alloc_stack(QFLT_LOCATION); // new location list + } return wp->w_llist; } -/* - * Copy the location list from window "from" to window "to". - */ -void copy_loclist(win_T *from, win_T *to) +/// Get the quickfix/location list stack to use for the specified Ex command. +/// For a location list command, returns the stack for the current window. If +/// the location list is not found, then returns NULL and prints an error +/// message if 'print_emsg' is TRUE. +static qf_info_T * qf_cmd_get_stack(exarg_T *eap, int print_emsg) +{ + qf_info_T *qi = &ql_info; + + if (is_loclist_cmd(eap->cmdidx)) { + qi = GET_LOC_LIST(curwin); + if (qi == NULL) { + if (print_emsg) { + EMSG(_(e_loclist)); + } + return NULL; + } + } + + return qi; +} + +/// Get the quickfix/location list stack to use for the specified Ex command. +/// For a location list command, returns the stack for the current window. +/// If the location list is not present, then allocates a new one. +/// For a location list command, sets 'pwinp' to curwin. +static qf_info_T *qf_cmd_get_or_alloc_stack(const exarg_T *eap, win_T **pwinp) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + qf_info_T *qi = &ql_info; + + if (is_loclist_cmd(eap->cmdidx)) { + qi = ll_get_or_alloc_list(curwin); + *pwinp = curwin; + } + + return qi; +} + +/// Copy location list entries from 'from_qfl' to 'to_qfl'. +static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) + FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi; - int idx; int i; + qfline_T *from_qfp; + + // copy all the location entries in this list + FOR_ALL_QFL_ITEMS(from_qfl, from_qfp, i) { + if (qf_add_entry(to_qfl, + NULL, + NULL, + from_qfp->qf_module, + 0, + from_qfp->qf_text, + from_qfp->qf_lnum, + from_qfp->qf_col, + from_qfp->qf_viscol, + from_qfp->qf_pattern, + from_qfp->qf_nr, + 0, + from_qfp->qf_valid) == QF_FAIL) { + return FAIL; + } - /* - * When copying from a location list window, copy the referenced - * location list. For other windows, copy the location list for - * that window. - */ - if (IS_LL_WINDOW(from)) + // qf_add_entry() will not set the qf_num field, as the + // directory and file names are not supplied. So the qf_fnum + // field is copied here. + qfline_T *const prevp = to_qfl->qf_last; + prevp->qf_fnum = from_qfp->qf_fnum; // file number + prevp->qf_type = from_qfp->qf_type; // error type + if (from_qfl->qf_ptr == from_qfp) { + to_qfl->qf_ptr = prevp; // current location + } + } + + return OK; +} + +/// Copy the specified location list 'from_qfl' to 'to_qfl'. +static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl) + FUNC_ATTR_NONNULL_ALL +{ + // Some of the fields are populated by qf_add_entry() + to_qfl->qfl_type = from_qfl->qfl_type; + to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; + to_qfl->qf_count = 0; + to_qfl->qf_index = 0; + to_qfl->qf_start = NULL; + to_qfl->qf_last = NULL; + to_qfl->qf_ptr = NULL; + if (from_qfl->qf_title != NULL) { + to_qfl->qf_title = vim_strsave(from_qfl->qf_title); + } else { + to_qfl->qf_title = NULL; + } + if (from_qfl->qf_ctx != NULL) { + to_qfl->qf_ctx = xcalloc(1, sizeof(*to_qfl->qf_ctx)); + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); + } else { + to_qfl->qf_ctx = NULL; + } + + if (from_qfl->qf_count) { + if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) { + return FAIL; + } + } + + to_qfl->qf_index = from_qfl->qf_index; // current index in the list + + // Assign a new ID for the location list + to_qfl->qf_id = ++last_qf_id; + to_qfl->qf_changedtick = 0L; + + // When no valid entries are present in the list, qf_ptr points to + // the first item in the list + if (to_qfl->qf_nonevalid) { + to_qfl->qf_ptr = to_qfl->qf_start; + to_qfl->qf_index = 1; + } + + return OK; +} + +// Copy the location list stack 'from' window to 'to' window. +void copy_loclist_stack(win_T *from, win_T *to) + FUNC_ATTR_NONNULL_ALL +{ + qf_info_T *qi; + + // When copying from a location list window, copy the referenced + // location list. For other windows, copy the location list for + // that window. + if (IS_LL_WINDOW(from)) { qi = from->w_llist_ref; - else + } else { qi = from->w_llist; + } - if (qi == NULL) /* no location list to copy */ + if (qi == NULL) { // no location list to copy return; + } - /* allocate a new location list */ - to->w_llist = ll_new_list(); + // allocate a new location list + to->w_llist = qf_alloc_stack(QFLT_LOCATION); to->w_llist->qf_listcount = qi->qf_listcount; - /* Copy the location lists one at a time */ - for (idx = 0; idx < qi->qf_listcount; idx++) { - qf_list_T *from_qfl; - qf_list_T *to_qfl; - + // Copy the location lists one at a time + for (int idx = 0; idx < qi->qf_listcount; idx++) { to->w_llist->qf_curlist = idx; - from_qfl = &qi->qf_lists[idx]; - to_qfl = &to->w_llist->qf_lists[idx]; - - /* Some of the fields are populated by qf_add_entry() */ - to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; - to_qfl->qf_count = 0; - to_qfl->qf_index = 0; - to_qfl->qf_start = NULL; - to_qfl->qf_last = NULL; - to_qfl->qf_ptr = NULL; - if (from_qfl->qf_title != NULL) - to_qfl->qf_title = vim_strsave(from_qfl->qf_title); - else - to_qfl->qf_title = NULL; - - if (from_qfl->qf_ctx != NULL) { - to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); - tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); - } else { - to_qfl->qf_ctx = NULL; - } - - if (from_qfl->qf_count) { - qfline_T *from_qfp; - qfline_T *prevp; - - // copy all the location entries in this list - for (i = 0, from_qfp = from_qfl->qf_start; - i < from_qfl->qf_count && from_qfp != NULL; - i++, from_qfp = from_qfp->qf_next) { - if (qf_add_entry(to->w_llist, - to->w_llist->qf_curlist, - NULL, - NULL, - from_qfp->qf_module, - 0, - from_qfp->qf_text, - from_qfp->qf_lnum, - from_qfp->qf_col, - from_qfp->qf_viscol, - from_qfp->qf_pattern, - from_qfp->qf_nr, - 0, - from_qfp->qf_valid) == FAIL) { - qf_free_all(to); - return; - } - /* - * qf_add_entry() will not set the qf_num field, as the - * directory and file names are not supplied. So the qf_fnum - * field is copied here. - */ - prevp = to->w_llist->qf_lists[to->w_llist->qf_curlist].qf_last; - prevp->qf_fnum = from_qfp->qf_fnum; // file number - prevp->qf_type = from_qfp->qf_type; // error type - if (from_qfl->qf_ptr == from_qfp) { - to_qfl->qf_ptr = prevp; // current location - } - } - } - - to_qfl->qf_index = from_qfl->qf_index; /* current index in the list */ - - // Assign a new ID for the location list - to_qfl->qf_id = ++last_qf_id; - to_qfl->qf_changedtick = 0L; - - /* When no valid entries are present in the list, qf_ptr points to - * the first item in the list */ - if (to_qfl->qf_nonevalid) { - to_qfl->qf_ptr = to_qfl->qf_start; - to_qfl->qf_index = 1; + if (copy_loclist(qf_get_list(qi, idx), + qf_get_list(to->w_llist, idx)) == FAIL) { + qf_free_all(to); + return; } } - to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ + to->w_llist->qf_curlist = qi->qf_curlist; // current list } -// Get buffer number for file "directory/fname". -// Also sets the b_has_qf_entry flag. -static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, - char_u *fname) +/// Get buffer number for file "directory/fname". +/// Also sets the b_has_qf_entry flag. +static int qf_get_fnum(qf_list_T *qfl, char_u *directory, char_u *fname ) { char_u *ptr = NULL; char_u *bufname; @@ -1721,7 +2094,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, // directory change. if (!os_path_exists(ptr)) { xfree(ptr); - directory = qf_guess_filepath(qi, qf_idx, fname); + directory = qf_guess_filepath(qfl, fname); if (directory) { ptr = (char_u *)concat_fnames((char *)directory, (char *)fname, true); } else { @@ -1749,7 +2122,7 @@ static int qf_get_fnum(qf_info_T *qi, int qf_idx, char_u *directory, return 0; } buf->b_has_qf_entry = - IS_QF_STACK(qi) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; + IS_QF_LIST(qfl) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; return buf->b_fnum; } @@ -1851,30 +2224,29 @@ static void qf_clean_dir_stack(struct dir_stack_T **stackptr) } } -// Check in which directory of the directory stack the given file can be -// found. -// Returns a pointer to the directory name or NULL if not found. -// Cleans up intermediate directory entries. -// -// TODO(vim): How to solve the following problem? -// If we have this directory tree: -// ./ -// ./aa -// ./aa/bb -// ./bb -// ./bb/x.c -// and make says: -// making all in aa -// making all in bb -// x.c:9: Error -// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. -// qf_guess_filepath will return NULL. -static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename) +/// Check in which directory of the directory stack the given file can be +/// found. +/// Returns a pointer to the directory name or NULL if not found. +/// Cleans up intermediate directory entries. +/// +/// TODO(vim): How to solve the following problem? +/// If we have this directory tree: +/// ./ +/// ./aa +/// ./aa/bb +/// ./bb +/// ./bb/x.c +/// and make says: +/// making all in aa +/// making all in bb +/// x.c:9: Error +/// Then qf_push_dir thinks we are in ./aa/bb, but we are in ./bb. +/// qf_guess_filepath will return NULL. +static char_u *qf_guess_filepath(qf_list_T *qfl, char_u *filename) { struct dir_stack_T *ds_ptr; struct dir_stack_T *ds_tmp; char_u *fullname; - qf_list_T *qfl = &qi->qf_lists[qf_idx]; // no dirs on the stack - there's nothing we can do if (qfl->qf_dir_stack == NULL) { @@ -1932,22 +2304,19 @@ static bool qflist_valid(win_T *wp, unsigned int qf_id) /// This may invalidate the current quickfix entry. This function checks /// whether an entry is still present in the quickfix list. /// Similar to location list. -static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) +static bool is_qf_entry_present(qf_list_T *qfl, qfline_T *qf_ptr) { - qf_list_T *qfl; qfline_T *qfp; int i; - qfl = &qi->qf_lists[qi->qf_curlist]; - // Search for the entry in the current list - for (i = 0, qfp = qfl->qf_start; i < qfl->qf_count; i++, qfp = qfp->qf_next) { - if (qfp == NULL || qfp == qf_ptr) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { + if (qfp == qf_ptr) { break; } } - if (i == qfl->qf_count) { // Entry is not found + if (i > qfl->qf_count) { // Entry is not found return false; } @@ -1956,20 +2325,19 @@ static bool is_qf_entry_present(qf_info_T *qi, qfline_T *qf_ptr) /// 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, +static qfline_T *get_next_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, int *qf_index, int dir) { int idx = *qf_index; int old_qf_fnum = qf_ptr->qf_fnum; do { - if (idx == qi->qf_lists[qi->qf_curlist].qf_count - || qf_ptr->qf_next == NULL) { + if (idx == qfl->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) + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) || (dir == FORWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; @@ -1978,7 +2346,7 @@ static qfline_T *get_next_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, /// 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, +static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr, int *qf_index, int dir) { int idx = *qf_index; @@ -1990,7 +2358,7 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, } idx--; qf_ptr = qf_ptr->qf_prev; - } while ((!qi->qf_lists[qi->qf_curlist].qf_nonevalid && !qf_ptr->qf_valid) + } while ((!qfl->qf_nonevalid && !qf_ptr->qf_valid) || (dir == BACKWARD_FILE && qf_ptr->qf_fnum == old_qf_fnum)); *qf_index = idx; @@ -2001,12 +2369,11 @@ static qfline_T *get_prev_valid_entry(qf_info_T *qi, qfline_T *qf_ptr, /// 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, +static qfline_T *get_nth_valid_entry(qf_list_T *qfl, 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--) { @@ -2014,9 +2381,9 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, prev_index = *qf_index; if (dir == FORWARD || dir == FORWARD_FILE) { - qf_ptr = get_next_valid_entry(qi, qf_ptr, qf_index, dir); + qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir); } else { - qf_ptr = get_prev_valid_entry(qi, qf_ptr, qf_index, dir); + qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir); } if (qf_ptr == NULL) { @@ -2036,7 +2403,7 @@ static qfline_T *get_nth_valid_entry(qf_info_T *qi, int errornr, } /// Get n'th (errornr) quickfix entry -static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, +static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr, int *cur_qfidx) { int qf_idx = *cur_qfidx; @@ -2049,7 +2416,7 @@ static qfline_T *get_nth_entry(qf_info_T *qi, int errornr, qfline_T *qf_ptr, // 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_idx < qfl->qf_count && qf_ptr->qf_next != NULL) { qf_idx++; qf_ptr = qf_ptr->qf_next; @@ -2071,6 +2438,13 @@ static win_T *qf_find_help_win(void) return NULL; } +/// Set the location list for the specified window to 'qi'. +static void win_set_loclist(win_T *wp, qf_info_T *qi) +{ + wp->w_llist = qi; + qi->qf_refcount++; +} + /// Find a help window or open one. static int jump_to_help_window(qf_info_T *qi, int *opened_window) { @@ -2110,8 +2484,7 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window) if (IS_LL_STACK(qi)) { // not a quickfix list // The new window should use the supplied location list - curwin->w_llist = qi; - qi->qf_refcount++; + win_set_loclist(curwin, qi); } } @@ -2147,7 +2520,7 @@ static win_T *qf_find_win_with_normal_buf(void) return NULL; } -// Go to a window in any tabpage containing the specified file. Returns TRUE +// Go to a window in any tabpage containing the specified file. Returns true // if successfully jumped to the window. Otherwise returns FALSE. static bool qf_goto_tabwin_with_file(int fnum) { @@ -2177,8 +2550,7 @@ static int qf_open_new_file_win(qf_info_T *ll_ref) 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++; + win_set_loclist(curwin, ll_ref); } return OK; } @@ -2219,9 +2591,10 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, // 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++; + if (win->w_llist == NULL && ll_ref != NULL) { + // The new window should use the location list from the + // location list window + win_set_loclist(win, ll_ref); } } @@ -2320,6 +2693,8 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, int *opened_window, int *abort) { + qf_list_T *qfl = qf_get_curlist(qi); + qfltype_T qfl_type = qfl->qfl_type; int retval = OK; if (qf_ptr->qf_type == 1) { @@ -2334,12 +2709,12 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, oldwin == curwin ? curwin : NULL); } } else { - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qfl->qf_id; retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); - if (IS_LL_STACK(qi)) { + if (qfl_type == QFLT_LOCATION) { // Location list. Check whether the associated window is still // present and the list is still valid. if (!win_valid_any_tab(oldwin)) { @@ -2350,8 +2725,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, EMSG(_(e_loc_list_changed)); *abort = true; } - } else if (!is_qf_entry_present(qi, qf_ptr)) { - if (IS_QF_STACK(qi)) { + } else if (!is_qf_entry_present(qfl, qf_ptr)) { + if (qfl_type == QFLT_QUICKFIX) { EMSG(_("E925: Current quickfix was changed")); } else { EMSG(_(e_loc_list_changed)); @@ -2373,9 +2748,6 @@ 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. @@ -2387,26 +2759,11 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, 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; - } - if (*line++ == '\t') { - curwin->w_cursor.col -= 7 - (screen_col % 8); - screen_col += 8 - (screen_col % 8); - } else { - screen_col++; - } - } + coladvance(qf_col - 1); + } else { + curwin->w_cursor.col = qf_col - 1; } curwin->w_set_curswant = true; check_cursor(); @@ -2417,7 +2774,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char_u qf_viscol, // 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)) { + if (!do_search(NULL, '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { curwin->w_cursor = save_cursor; } } @@ -2433,7 +2790,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, update_topline_redraw(); } snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, - qi->qf_lists[qi->qf_curlist].qf_count, + qf_get_curlist(qi)->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. @@ -2463,6 +2820,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, /// else go to entry "errornr" void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) { + qf_list_T *qfl; qfline_T *qf_ptr; qfline_T *old_qf_ptr; int qf_index; @@ -2480,32 +2838,34 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) if (qi == NULL) qi = &ql_info; - if (qi->qf_curlist >= qi->qf_listcount - || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } - qf_ptr = qi->qf_lists[qi->qf_curlist].qf_ptr; + qfl = qf_get_curlist(qi); + + qf_ptr = qfl->qf_ptr; old_qf_ptr = qf_ptr; - qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + qf_index = qfl->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); + qf_ptr = get_nth_valid_entry(qfl, 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); + qf_ptr = get_nth_entry(qfl, 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; + qfl->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)) { @@ -2574,8 +2934,8 @@ failed: } theend: if (qi != NULL) { - qi->qf_lists[qi->qf_curlist].qf_ptr = qf_ptr; - qi->qf_lists[qi->qf_curlist].qf_index = qf_index; + qfl->qf_ptr = qf_ptr; + qfl->qf_index = qf_index; } if (p_swb != old_swb && opened_window) { /* Restore old 'switchbuf' value, but not when an autocommand or @@ -2588,36 +2948,117 @@ theend: } } + +// Highlight attributes used for displaying entries from the quickfix list. +static int qfFileAttr; +static int qfSepAttr; +static int qfLineAttr; + +/// Display information about a single entry from the quickfix/location list. +/// Used by ":clist/:llist" commands. +/// 'cursel' will be set to true for the currently selected entry in the +/// quickfix list. +static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) +{ + char_u *fname; + buf_T *buf; + + fname = NULL; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, + (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", qf_idx); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", + qf_idx, (char *)fname); + } + } + + // Support for filtering entries using :filter /pat/ clist + // Match against the module name, file name, search pattern and + // text of the entry. + bool filter_entry = true; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + filter_entry &= message_filtered(qfp->qf_module); + } + if (filter_entry && fname != NULL) { + filter_entry &= message_filtered(fname); + } + if (filter_entry && qfp->qf_pattern != NULL) { + filter_entry &= message_filtered(qfp->qf_pattern); + } + if (filter_entry) { + filter_entry &= message_filtered(qfp->qf_text); + } + if (filter_entry) { + return; + } + + msg_putchar('\n'); + msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr); + + if (qfp->qf_lnum != 0) { + msg_puts_attr(":", qfSepAttr); + } + if (qfp->qf_lnum == 0) { + IObuff[0] = NUL; + } else if (qfp->qf_col == 0) { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); + } else { + vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", + qfp->qf_lnum, qfp->qf_col); + } + vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", + (char *)qf_types(qfp->qf_type, qfp->qf_nr)); + msg_puts_attr((const char *)IObuff, qfLineAttr); + msg_puts_attr(":", qfSepAttr); + if (qfp->qf_pattern != NULL) { + qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); + msg_puts((const char *)IObuff); + msg_puts_attr(":", qfSepAttr); + } + msg_puts(" "); + + // Remove newlines and leading whitespace from the text. For an + // unrecognized line keep the indent, the compiler may mark a word + // with ^^^^. */ + qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) + ? skipwhite(qfp->qf_text) : qfp->qf_text, + IObuff, IOSIZE); + msg_prt_line(IObuff, false); + ui_flush(); // show one line at a time +} + /* * ":clist": list all errors * ":llist": list all locations */ void qf_list(exarg_T *eap) { - buf_T *buf; - char_u *fname; - qfline_T *qfp; + qf_list_T *qfl; + qfline_T *qfp; int i; int idx1 = 1; int idx2 = -1; char_u *arg = eap->arg; - int qfFileAttr; - int qfSepAttr; - int qfLineAttr; int all = eap->forceit; // if not :cl!, only show // recognised errors - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (eap->cmdidx == CMD_llist) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } - if (qi->qf_curlist >= qi->qf_listcount - || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -2631,12 +3072,13 @@ void qf_list(exarg_T *eap) EMSG(_(e_trailing)); return; } + qfl = qf_get_curlist(qi); if (plus) { - i = qi->qf_lists[qi->qf_curlist].qf_index; + i = qfl->qf_index; idx2 = i + idx1; idx1 = i; } else { - i = qi->qf_lists[qi->qf_curlist].qf_count; + i = qfl->qf_count; if (idx1 < 0) { idx1 = (-idx1 > i) ? 0 : idx1 + i + 1; } @@ -2663,95 +3105,13 @@ void qf_list(exarg_T *eap) qfLineAttr = HL_ATTR(HLF_N); } - if (qi->qf_lists[qi->qf_curlist].qf_nonevalid) { + if (qfl->qf_nonevalid) { all = true; } - qfp = qi->qf_lists[qi->qf_curlist].qf_start; - for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { - if (got_int) { - break; - } - - fname = NULL; - 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); - } - } - - // Support for filtering entries using :filter /pat/ clist - // Match against the module name, file name, search pattern and - // text of the entry. - bool filter_entry = true; - if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - filter_entry &= message_filtered(qfp->qf_module); - } - if (filter_entry && fname != NULL) { - filter_entry &= message_filtered(fname); - } - if (filter_entry && qfp->qf_pattern != NULL) { - filter_entry &= message_filtered(qfp->qf_pattern); - } - if (filter_entry) { - filter_entry &= message_filtered(qfp->qf_text); - } - if (filter_entry) { - goto next_entry; - } - msg_putchar('\n'); - msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index - ? HL_ATTR(HLF_QFL) : qfFileAttr); - - if (qfp->qf_lnum != 0) { - msg_puts_attr(":", qfSepAttr); - } - if (qfp->qf_lnum == 0) { - IObuff[0] = NUL; - } else if (qfp->qf_col == 0) { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR, qfp->qf_lnum); - } else { - vim_snprintf((char *)IObuff, IOSIZE, "%" PRIdLINENR " col %d", - qfp->qf_lnum, qfp->qf_col); - } - vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", - (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - msg_puts_attr((const char *)IObuff, qfLineAttr); - msg_puts_attr(":", qfSepAttr); - if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); - msg_puts((const char *)IObuff); - msg_puts_attr(":", qfSepAttr); - } - msg_puts(" "); - - /* Remove newlines and leading whitespace from the text. For an - * unrecognized line keep the indent, the compiler may mark a word - * with ^^^^. */ - qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) - ? skipwhite(qfp->qf_text) : qfp->qf_text, - IObuff, IOSIZE); - msg_prt_line(IObuff, FALSE); - ui_flush(); /* show one line at a time */ - } - -next_entry: - qfp = qfp->qf_next; - if (qfp == NULL) { - break; + qf_list_entry(qfp, i, i == qfl->qf_index); } - i++; os_breakcheck(); } } @@ -2760,10 +3120,12 @@ next_entry: * Remove newlines and leading whitespace from an error message. * Put the result in "buf[bufsize]". */ -static void qf_fmt_text(char_u *text, char_u *buf, int bufsize) +static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf, + int bufsize) + FUNC_ATTR_NONNULL_ALL { int i; - char_u *p = text; + const char_u *p = text; for (i = 0; *p != NUL && i < bufsize - 1; ++i) { if (*p == '\n') { @@ -2804,23 +3166,17 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) msg(buf); } -/* - * ":colder [count]": Up in the quickfix stack. - * ":cnewer [count]": Down in the quickfix stack. - * ":lolder [count]": Up in the location list stack. - * ":lnewer [count]": Down in the location list stack. - */ +/// ":colder [count]": Up in the quickfix stack. +/// ":cnewer [count]": Down in the quickfix stack. +/// ":lolder [count]": Up in the location list stack. +/// ":lnewer [count]": Down in the location list stack. void qf_age(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; int count; - if (eap->cmdidx == CMD_lolder || eap->cmdidx == CMD_lnewer) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } if (eap->addr_count != 0) { @@ -2851,14 +3207,10 @@ void qf_age(exarg_T *eap) /// Display the information about all the quickfix/location lists in the stack. void qf_history(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi = qf_cmd_get_stack(eap, false); int i; - if (eap->cmdidx == CMD_lhistory) { - qi = GET_LOC_LIST(curwin); - } - if (qi == NULL || (qi->qf_listcount == 0 - && qi->qf_lists[qi->qf_curlist].qf_count == 0)) { + if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { MSG(_("No entries")); } else { for (i = 0; i < qi->qf_listcount; i++) { @@ -2869,12 +3221,11 @@ void qf_history(exarg_T *eap) /// Free all the entries in the error list "idx". Note that other information /// associated with the list like context and title are not freed. -static void qf_free_items(qf_info_T *qi, int idx) +static void qf_free_items(qf_list_T *qfl) { qfline_T *qfp; qfline_T *qfpnext; bool stop = false; - qf_list_T *qfl = &qi->qf_lists[idx]; while (qfl->qf_count && qfl->qf_start != NULL) { qfp = qfl->qf_start; @@ -2915,10 +3266,9 @@ static void qf_free_items(qf_info_T *qi, int idx) /// Free error list "idx". Frees all the entries in the quickfix list, /// associated context information and the title. -static void qf_free(qf_info_T *qi, int idx) +static void qf_free(qf_list_T *qfl) { - qf_list_T *qfl = &qi->qf_lists[idx]; - qf_free_items(qi, idx); + qf_free_items(qfl); XFREE_CLEAR(qfl->qf_title); tv_free(qfl->qf_ctx); @@ -2950,11 +3300,10 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qi = wp->w_llist; } - for (idx = 0; idx < qi->qf_listcount; ++idx) - if (qi->qf_lists[idx].qf_count) - for (i = 0, qfp = qi->qf_lists[idx].qf_start; - i < qi->qf_lists[idx].qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + for (idx = 0; idx < qi->qf_listcount; idx++) { + qf_list_T *qfl = qf_get_list(qi, idx); + if (!qf_list_empty(qfl)) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (qfp->qf_fnum == curbuf->b_fnum) { found_one = true; if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) { @@ -2966,6 +3315,8 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, qfp->qf_lnum += amount_after; } } + } + } return found_one; } @@ -3025,7 +3376,7 @@ void qf_view_result(bool split) if (IS_LL_WINDOW(curwin)) { qi = GET_LOC_LIST(curwin); } - if (qf_list_empty(qi, qi->qf_curlist)) { + if (qf_list_empty(qf_get_curlist(qi))) { EMSG(_(e_quickfix)); return; } @@ -3053,15 +3404,16 @@ void qf_view_result(bool split) */ void ex_cwindow(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; + qf_list_T *qfl; win_T *win; - if (eap->cmdidx == CMD_lwindow) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) - return; + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } + qfl = qf_get_curlist(qi); + /* Look for an existing quickfix window. */ win = qf_find_win(qi); @@ -3070,13 +3422,15 @@ void ex_cwindow(exarg_T *eap) * close the window. If a quickfix window is not open, then open * it if we have errors; otherwise, leave it closed. */ - if (qi->qf_lists[qi->qf_curlist].qf_nonevalid - || qi->qf_lists[qi->qf_curlist].qf_count == 0 - || qi->qf_curlist >= qi->qf_listcount) { - if (win != NULL) + if (qf_stack_empty(qi) + || qfl->qf_nonevalid + || qf_list_empty(qf_get_curlist(qi))) { + if (win != NULL) { ex_cclose(eap); - } else if (win == NULL) + } + } else if (win == NULL) { ex_copen(eap); + } } /* @@ -3085,13 +3439,11 @@ void ex_cwindow(exarg_T *eap) */ void ex_cclose(exarg_T *eap) { - win_T *win = NULL; - qf_info_T *qi = &ql_info; + win_T *win = NULL; + qf_info_T *qi; - if (eap->cmdidx == CMD_lclose || eap->cmdidx == CMD_lwindow) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) - return; + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return; } /* Find existing quickfix window and close it. */ @@ -3101,27 +3453,124 @@ void ex_cclose(exarg_T *eap) } } -/* - * ":copen": open a window that shows the list of errors. - * ":lopen": open a window that shows the location list. - */ +// Goto a quickfix or location list window (if present). +// Returns OK if the window is found, FAIL otherwise. +static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, + bool vertsplit) +{ + win_T *const win = qf_find_win(qi); + if (win == NULL) { + return FAIL; + } + + win_goto(win); + if (resize) { + if (vertsplit) { + if (sz != win->w_width) { + win_setwidth(sz); + } + } else if (sz != win->w_height + && (win->w_height + win->w_status_height + tabline_height() + < cmdline_row)) { + win_setheight(sz); + } + } + + return OK; +} + +// Open a new quickfix or location list window, load the quickfix buffer and +// set the appropriate options for the window. +// Returns FAIL if the window could not be opened. +static int qf_open_new_cwindow(const qf_info_T *qi, int height) +{ + win_T *oldwin = curwin; + const tabpage_T *const prevtab = curtab; + int flags = 0; + + const buf_T *const qf_buf = qf_find_buf(qi); + + // The current window becomes the previous window afterwards. + win_T *const win = curwin; + + if (IS_QF_STACK(qi) && cmdmod.split == 0) { + // Create the new quickfix window at the very bottom, except when + // :belowright or :aboveleft is used. + win_goto(lastwin); + } + // Default is to open the window below the current window + if (cmdmod.split == 0) { + flags = WSP_BELOW; + } + flags |= WSP_NEWLOC; + if (win_split(height, flags) == FAIL) { + return FAIL; // not enough room for window + } + RESET_BINDING(curwin); + + if (IS_LL_STACK(qi)) { + // For the location list window, create a reference to the + // location list from the window 'win'. + curwin->w_llist_ref = win->w_llist; + win->w_llist->qf_refcount++; + } + + if (oldwin != curwin) { + oldwin = NULL; // don't store info when in another window + } + if (qf_buf != NULL) { + // Use the existing quickfix buffer + (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, + ECMD_HIDE + ECMD_OLDBUF, oldwin); + } else { + // Create a new quickfix buffer + (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); + + // switch off 'swapfile' + set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value("bt", 0L, "quickfix", OPT_LOCAL); + set_option_value("bh", 0L, "wipe", OPT_LOCAL); + RESET_BINDING(curwin); + curwin->w_p_diff = false; + set_option_value("fdm", 0L, "manual", OPT_LOCAL); + } + + // Only set the height when still in the same tab page and there is no + // window to the side. + if (curtab == prevtab && curwin->w_width == Columns) { + win_setheight(height); + } + curwin->w_p_wfh = true; // set 'winfixheight' + if (win_valid(win)) { + prevwin = win; + } + return OK; +} + +/// Set "w:quickfix_title" if "qi" has a title. +static void qf_set_title_var(qf_list_T *qfl) +{ + if (qfl->qf_title != NULL) { + set_internal_string_var((char_u *)"w:quickfix_title", qfl->qf_title); + } +} + +/// ":copen": open a window that shows the list of errors. +/// ":lopen": open a window that shows the location list. void ex_copen(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; + qf_list_T *qfl; int height; - win_T *win; - tabpage_T *prevtab = curtab; - buf_T *qf_buf; - win_T *oldwin = curwin; + int status = FAIL; + int lnum; - if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } + incr_quickfix_busy(); + if (eap->addr_count != 0) { assert(eap->line2 <= INT_MAX); height = (int)eap->line2; @@ -3130,94 +3579,33 @@ void ex_copen(exarg_T *eap) } reset_VIsual_and_resel(); // stop Visual mode - /* - * Find existing quickfix window, or open a new one. - */ - win = qf_find_win(qi); - - if (win != NULL && cmdmod.tab == 0) { - win_goto(win); - if (eap->addr_count != 0) { - if (cmdmod.split & WSP_VERT) { - if (height != win->w_width) { - win_setwidth(height); - } - } else { - if (height != win->w_height) { - win_setheight(height); - } - } - } - } else { - int flags = 0; - - qf_buf = qf_find_buf(qi); - - /* The current window becomes the previous window afterwards. */ - win = curwin; - - if ((eap->cmdidx == CMD_copen || eap->cmdidx == CMD_cwindow) - && cmdmod.split == 0) - // Create the new quickfix window at the very bottom, except when - // :belowright or :aboveleft is used. - win_goto(lastwin); - // Default is to open the window below the current window - if (cmdmod.split == 0) { - flags = WSP_BELOW; - } - flags |= WSP_NEWLOC; - if (win_split(height, flags) == FAIL) { - return; // not enough room for window + // Find an existing quickfix window, or open a new one. + if (cmdmod.tab == 0) { + status = qf_goto_cwindow(qi, eap->addr_count != 0, height, + cmdmod.split & WSP_VERT); + } + if (status == FAIL) { + if (qf_open_new_cwindow(qi, height) == FAIL) { + decr_quickfix_busy(); + return; } - RESET_BINDING(curwin); + } - if (eap->cmdidx == CMD_lopen || eap->cmdidx == CMD_lwindow) { - /* - * For the location list window, create a reference to the - * location list from the window 'win'. - */ - curwin->w_llist_ref = win->w_llist; - win->w_llist->qf_refcount++; - } - - if (oldwin != curwin) - oldwin = NULL; /* don't store info when in another window */ - if (qf_buf != NULL) - /* Use the existing quickfix buffer */ - (void)do_ecmd(qf_buf->b_fnum, NULL, NULL, NULL, ECMD_ONE, - ECMD_HIDE + ECMD_OLDBUF, oldwin); - else { - /* Create a new quickfix buffer */ - (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin); - // Switch off 'swapfile'. - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - RESET_BINDING(curwin); - curwin->w_p_diff = false; - set_option_value("fdm", 0L, "manual", OPT_LOCAL); - } - - /* Only set the height when still in the same tab page and there is no - * window to the side. */ - if (curtab == prevtab - && curwin->w_width == Columns - ) - win_setheight(height); - curwin->w_p_wfh = TRUE; /* set 'winfixheight' */ - if (win_valid(win)) - prevwin = win; - } - - qf_set_title_var(qi); + qfl = qf_get_curlist(qi); + qf_set_title_var(qfl); + // Save the current index here, as updating the quickfix buffer may free + // the quickfix list + lnum = qfl->qf_index; // Fill the buffer with the quickfix list. - qf_fill_buffer(qi, curbuf, NULL); + qf_fill_buffer(qfl, curbuf, NULL); + + decr_quickfix_busy(); - curwin->w_cursor.lnum = qi->qf_lists[qi->qf_curlist].qf_index; + curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = 0; check_cursor(); - update_topline(); /* scroll to show the line */ + update_topline(); // scroll to show the line } // Move the cursor in the quickfix window to "lnum". @@ -3238,17 +3626,13 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curbuf = curwin->w_buffer; } -// :cbottom/:lbottom command. +/// :cbottom/:lbottom command. void ex_cbottom(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (eap->cmdidx == CMD_lbottom) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } win_T *win = qf_find_win(qi); @@ -3270,7 +3654,7 @@ linenr_T qf_current_entry(win_T *wp) /* In the location list window, use the referenced location list */ qi = wp->w_llist_ref; - return qi->qf_lists[qi->qf_curlist].qf_index; + return qf_get_curlist(qi)->qf_index; } /* @@ -3284,7 +3668,7 @@ qf_win_pos_update ( ) { win_T *win; - int qf_index = qi->qf_lists[qi->qf_curlist].qf_index; + int qf_index = qf_get_curlist(qi)->qf_index; /* * Put the cursor on the current error in the quickfix window, so that @@ -3307,8 +3691,9 @@ qf_win_pos_update ( } /// Checks whether the given window is displaying the specified -/// quickfix/location list buffer. -static int is_qf_win(win_T *win, qf_info_T *qi) +/// quickfix/location stack. +static int is_qf_win(const win_T *win, const qf_info_T *qi) + FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // // A window displaying the quickfix buffer will have the w_llist_ref field @@ -3326,9 +3711,10 @@ static int is_qf_win(win_T *win, qf_info_T *qi) return false; } -/// Find a window displaying the quickfix/location list 'qi' +/// Find a window displaying the quickfix/location stack 'qi' /// Only searches in the current tabpage. -static win_T *qf_find_win(qf_info_T *qi) +static win_T *qf_find_win(const qf_info_T *qi) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (is_qf_win(win, qi)) { @@ -3343,7 +3729,8 @@ static win_T *qf_find_win(qf_info_T *qi) * Find a quickfix buffer. * Searches in windows opened in all the tabs. */ -static buf_T *qf_find_buf(qf_info_T *qi) +static buf_T *qf_find_buf(const qf_info_T *qi) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { FOR_ALL_TAB_WINDOWS(tp, win) { if (is_qf_win(win, qi)) { @@ -3362,7 +3749,7 @@ static void qf_update_win_titlevar(qf_info_T *qi) if ((win = qf_find_win(qi)) != NULL) { win_T *curwin_save = curwin; curwin = win; - qf_set_title_var(qi); + qf_set_title_var(qf_get_curlist(qi)); curwin = curwin_save; } } @@ -3388,7 +3775,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) qf_update_win_titlevar(qi); - qf_fill_buffer(qi, buf, old_last); + qf_fill_buffer(qf_get_curlist(qi), buf, old_last); buf_inc_changedtick(buf); if (old_last == NULL) { @@ -3406,26 +3793,82 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) } } -// Set "w:quickfix_title" if "qi" has a title. -static void qf_set_title_var(qf_info_T *qi) +// Add an error line to the quickfix buffer. +static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp, + char_u *dirname) + FUNC_ATTR_NONNULL_ALL { - if (qi->qf_lists[qi->qf_curlist].qf_title != NULL) { - set_internal_string_var((char_u *)"w:quickfix_title", - qi->qf_lists[qi->qf_curlist].qf_title); + int len; + buf_T *errbuf; + + 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 { + // shorten the file name if not done already + if (errbuf->b_sfname == NULL + || path_is_absolute(errbuf->b_sfname)) { + if (*dirname == NUL) { + os_dirname(dirname, MAXPATHL); + } + shorten_buf_fname(errbuf, dirname, false); + } + STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); + } + len = (int)STRLEN(IObuff); + } else { + len = 0; + } + IObuff[len++] = '|'; + + if (qfp->qf_lnum > 0) { + snprintf((char *)IObuff + len, sizeof(IObuff), "%" PRId64, + (int64_t)qfp->qf_lnum); + len += (int)STRLEN(IObuff + len); + + if (qfp->qf_col > 0) { + snprintf((char *)IObuff + len, sizeof(IObuff), " col %d", qfp->qf_col); + len += (int)STRLEN(IObuff + len); + } + + snprintf((char *)IObuff + len, sizeof(IObuff), "%s", + (char *)qf_types(qfp->qf_type, qfp->qf_nr)); + len += (int)STRLEN(IObuff + len); + } else if (qfp->qf_pattern != NULL) { + qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); + len += (int)STRLEN(IObuff + len); + } + IObuff[len++] = '|'; + IObuff[len++] = ' '; + + // Remove newlines and leading whitespace from the text. + // For an unrecognized line keep the indent, the compiler may + // mark a word with ^^^^. + qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, + IObuff + len, IOSIZE - len); + + if (ml_append_buf(buf, lnum, IObuff, + (colnr_T)STRLEN(IObuff) + 1, false) == FAIL) { + return FAIL; } + return OK; } -// Fill current buffer with quickfix errors, replacing any previous contents. -// curbuf must be the quickfix buffer! -// If "old_last" is not NULL append the items after this one. -// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() -// is used and autocommands will be triggered. -static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) +/// Fill current buffer with quickfix errors, replacing any previous contents. +/// curbuf must be the quickfix buffer! +/// If "old_last" is not NULL append the items after this one. +/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete() +/// is used and autocommands will be triggered. +static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last) + FUNC_ATTR_NONNULL_ARG(2) { linenr_T lnum; qfline_T *qfp; - buf_T *errbuf; - int len; const bool old_KeyTyped = KeyTyped; if (old_last == NULL) { @@ -3440,73 +3883,22 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) } } - /* Check if there is anything to display */ - if (qi->qf_curlist < qi->qf_listcount) { + // Check if there is anything to display + if (qfl != NULL) { char_u dirname[MAXPATHL]; *dirname = NUL; // Add one line for each error if (old_last == NULL) { - qfp = qi->qf_lists[qi->qf_curlist].qf_start; + qfp = qfl->qf_start; lnum = 0; } else { qfp = old_last->qf_next; lnum = buf->b_ml.ml_line_count; } - while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) { - 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 { - // shorten the file name if not done already - if (errbuf->b_sfname == NULL - || path_is_absolute(errbuf->b_sfname)) { - if (*dirname == NUL) { - os_dirname(dirname, MAXPATHL); - } - shorten_buf_fname(errbuf, dirname, false); - } - STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff)); - } - len = (int)STRLEN(IObuff); - } else { - len = 0; - } - IObuff[len++] = '|'; - - if (qfp->qf_lnum > 0) { - sprintf((char *)IObuff + len, "%" PRId64, (int64_t)qfp->qf_lnum); - len += (int)STRLEN(IObuff + len); - - if (qfp->qf_col > 0) { - sprintf((char *)IObuff + len, " col %d", qfp->qf_col); - len += (int)STRLEN(IObuff + len); - } - - sprintf((char *)IObuff + len, "%s", - (char *)qf_types(qfp->qf_type, qfp->qf_nr)); - len += (int)STRLEN(IObuff + len); - } else if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); - len += (int)STRLEN(IObuff + len); - } - IObuff[len++] = '|'; - IObuff[len++] = ' '; - - /* Remove newlines and leading whitespace from the text. - * For an unrecognized line keep the indent, the compiler may - * mark a word with ^^^^. */ - qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, - IObuff + len, IOSIZE - len); - - if (ml_append_buf(buf, lnum, IObuff, (colnr_T)STRLEN(IObuff) + 1, false) - == FAIL) { + while (lnum < qfl->qf_count) { + if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) { break; } lnum++; @@ -3548,9 +3940,9 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) KeyTyped = old_KeyTyped; } -static void qf_list_changed(qf_info_T *qi, int qf_idx) +static void qf_list_changed(qf_list_T *qfl) { - qi->qf_lists[qf_idx].qf_changedtick++; + qfl->qf_changedtick++; } /// Return the quickfix/location list number with the given identifier. @@ -3566,15 +3958,15 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) return INVALID_QFIDX; } -// If the current list is not "save_qfid" and we can find the list with that ID -// then make it the current list. -// This is used when autocommands may have changed the current list. -// Returns OK if successfully restored the list. Returns FAIL if the list with -// the specified identifier (save_qfid) is not found in the stack. +/// If the current list is not "save_qfid" and we can find the list with that ID +/// then make it the current list. +/// This is used when autocommands may have changed the current list. +/// Returns OK if successfully restored the list. Returns FAIL if the list with +/// the specified identifier (save_qfid) is not found in the stack. static int qf_restore_list(qf_info_T *qi, unsigned save_qfid) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (qi->qf_lists[qi->qf_curlist].qf_id != save_qfid) { + if (qf_get_curlist(qi)->qf_id != save_qfid) { const int curlist = qf_id2nr(qi, save_qfid); if (curlist < 0) { // list is not present @@ -3593,7 +3985,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) return; } // Autocommands might have cleared the list, check for that - if (!qf_list_empty(qi, qi->qf_curlist)) { + if (!qf_list_empty(qf_get_curlist(qi))) { qf_jump(qi, 0, 0, forceit); } } @@ -3611,6 +4003,58 @@ int grep_internal(cmdidx_T cmdidx) *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0; } +// Return the make/grep autocmd name. +static char_u *make_get_auname(cmdidx_T cmdidx) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (cmdidx) { + case CMD_make: + return (char_u *)"make"; + case CMD_lmake: + return (char_u *)"lmake"; + 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; + } +} + +// Form the complete command line to invoke 'make'/'grep'. Quote the command +// using 'shellquote' and append 'shellpipe'. Echo the fully formed command. +static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET +{ + size_t len = STRLEN(p_shq) * 2 + STRLEN(makecmd) + 1; + if (*p_sp != NUL) { + len += STRLEN(p_sp) + STRLEN(fname) + 3; + } + char *const cmd = xmalloc(len); + snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)makecmd, (char *)p_shq); + + // If 'shellpipe' empty: don't redirect to 'errorfile'. + if (*p_sp != NUL) { + append_redir(cmd, len, (char *)p_sp, (char *)fname); + } + + // Display the fully formed command. Output a newline if there's something + // else than the :make command that was typed (in which case the cursor is + // in column 0). + if (msg_col == 0) { + msg_didout = false; + } + msg_start(); + MSG_PUTS(":!"); + msg_outtrans((char_u *)cmd); // show what we are doing + + return cmd; +} + /* * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" */ @@ -3620,24 +4064,15 @@ void ex_make(exarg_T *eap) win_T *wp = NULL; qf_info_T *qi = &ql_info; int res; - char_u *au_name = NULL; char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; - /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ + // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". if (grep_internal(eap->cmdidx)) { ex_vimgrep(eap); return; } - switch (eap->cmdidx) { - case CMD_make: au_name = (char_u *)"make"; break; - case CMD_lmake: au_name = (char_u *)"lmake"; 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; - } + char_u *const au_name = make_get_auname(eap->cmdidx); if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { @@ -3645,9 +4080,9 @@ void ex_make(exarg_T *eap) } } - if (eap->cmdidx == CMD_lmake || eap->cmdidx == CMD_lgrep - || eap->cmdidx == CMD_lgrepadd) + if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; + } autowrite_all(); fname = get_mef_name(); @@ -3655,28 +4090,11 @@ void ex_make(exarg_T *eap) return; os_remove((char *)fname); // in case it's not unique - // If 'shellpipe' empty: don't redirect to 'errorfile'. - const size_t len = (STRLEN(p_shq) * 2 + STRLEN(eap->arg) + 1 - + (*p_sp == NUL - ? 0 - : STRLEN(p_sp) + STRLEN(fname) + 3)); - char *const cmd = xmalloc(len); - snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)eap->arg, - (char *)p_shq); - if (*p_sp != NUL) { - append_redir(cmd, len, (char *) p_sp, (char *) fname); - } - // Output a newline if there's something else than the :make command that - // was typed (in which case the cursor is in column 0). - if (msg_col == 0) { - msg_didout = false; - } - msg_start(); - MSG_PUTS(":!"); - msg_outtrans((char_u *)cmd); // show what we are doing + char *const cmd = make_get_fullcmd(eap->arg, fname); do_shell((char_u *)cmd, 0); + incr_quickfix_busy(); res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, @@ -3689,11 +4107,11 @@ void ex_make(exarg_T *eap) } } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); @@ -3704,6 +4122,7 @@ void ex_make(exarg_T *eap) } cleanup: + decr_quickfix_busy(); os_remove((char *)fname); xfree(fname); xfree(cmd); @@ -3761,23 +4180,20 @@ static char_u *get_mef_name(void) size_t qf_get_size(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; - if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 0; - } + qf_info_T *qi; + qf_list_T *qfl; + + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 0; } int prev_fnum = 0; size_t sz = 0; qfline_T *qfp; - size_t i; - assert(qi->qf_lists[qi->qf_curlist].qf_count >= 0); - for (i = 0, qfp = qi->qf_lists[qi->qf_curlist].qf_start; - i < (size_t)qi->qf_lists[qi->qf_curlist].qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + int i; + assert(qf_get_curlist(qi)->qf_count >= 0); + qfl = qf_get_curlist(qi); + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (!qfp->qf_valid) { continue; } @@ -3800,18 +4216,14 @@ size_t qf_get_size(exarg_T *eap) size_t qf_get_cur_idx(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 0; - } + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 0; } - assert(qi->qf_lists[qi->qf_curlist].qf_index >= 0); - return (size_t)qi->qf_lists[qi->qf_curlist].qf_index; + assert(qf_get_curlist(qi)->qf_index >= 0); + return (size_t)qf_get_curlist(qi)->qf_index; } /// Returns the current index in the quickfix/location list, @@ -3820,20 +4232,16 @@ size_t qf_get_cur_idx(exarg_T *eap) int qf_get_cur_valid_idx(exarg_T *eap) FUNC_ATTR_NONNULL_ALL { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) { - // Location list. - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - return 1; - } + if ((qi = qf_cmd_get_stack(eap, false)) == NULL) { + return 1; } - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; + qf_list_T *qfl = qf_get_curlist(qi); // Check if the list has valid errors. - if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { + if (!qf_list_has_valid_entries(qfl)) { return 1; } @@ -3868,24 +4276,20 @@ int qf_get_cur_valid_idx(exarg_T *eap) /// Used by :cdo, :ldo, :cfdo and :lfdo commands. /// For :cdo and :ldo, returns the 'n'th valid error entry. /// For :cfdo and :lfdo, returns the 'n'th valid file entry. -static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo) +static size_t qf_get_nth_valid_entry(qf_list_T *qfl, size_t n, int fdo) FUNC_ATTR_NONNULL_ALL { - qf_list_T *qfl = &qi->qf_lists[qi->qf_curlist]; - // Check if the list has valid errors. - if (qfl->qf_count <= 0 || qfl->qf_nonevalid) { + if (!qf_list_has_valid_entries(qfl)) { return 1; } int prev_fnum = 0; size_t eidx = 0; - size_t i; + int i; qfline_T *qfp; assert(qfl->qf_count >= 0); - for (i = 1, qfp = qfl->qf_start; - i <= (size_t)qfl->qf_count && qfp != NULL; - i++, qfp = qfp->qf_next) { + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { if (qfp->qf_valid) { if (fdo) { if (qfp->qf_fnum > 0 && qfp->qf_fnum != prev_fnum) { @@ -3903,41 +4307,39 @@ static size_t qf_get_nth_valid_entry(qf_info_T *qi, size_t n, bool fdo) } } - return i <= (size_t)qfl->qf_count ? i : 1; + return i <= qfl->qf_count ? (size_t)i : 1; } -/* - * ":cc", ":crewind", ":cfirst" and ":clast". - * ":ll", ":lrewind", ":lfirst" and ":llast". - * ":cdo", ":ldo", ":cfdo" and ":lfdo". - */ +/// ":cc", ":crewind", ":cfirst" and ":clast". +/// ":ll", ":lrewind", ":lfirst" and ":llast". +/// ":cdo", ":ldo", ":cfdo" and ":lfdo". void ex_cc(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (eap->cmdidx == CMD_ll - || eap->cmdidx == CMD_lrewind - || eap->cmdidx == CMD_lfirst - || eap->cmdidx == CMD_llast - || eap->cmdidx == CMD_ldo - || eap->cmdidx == CMD_lfdo) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } int errornr; if (eap->addr_count > 0) { errornr = (int)eap->line2; - } else if (eap->cmdidx == CMD_cc || eap->cmdidx == CMD_ll) { - errornr = 0; - } else if (eap->cmdidx == CMD_crewind || eap->cmdidx == CMD_lrewind - || eap->cmdidx == CMD_cfirst || eap->cmdidx == CMD_lfirst) { - errornr = 1; } else { - errornr = 32767; + switch (eap->cmdidx) { + case CMD_cc: + case CMD_ll: + errornr = 0; + break; + case CMD_crewind: + case CMD_lrewind: + case CMD_cfirst: + case CMD_lfirst: + errornr = 1; + break; + default: + errornr = 32767; + break; + } } // For cdo and ldo commands, jump to the nth valid error. @@ -3951,8 +4353,9 @@ void ex_cc(exarg_T *eap) } else { n = 1; } - size_t valid_entry = qf_get_nth_valid_entry(qi, n, - eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); + size_t valid_entry = qf_get_nth_valid_entry( + qf_get_curlist(qi), n, + eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo); assert(valid_entry <= INT_MAX); errornr = (int)valid_entry; } @@ -3960,28 +4363,15 @@ void ex_cc(exarg_T *eap) qf_jump(qi, 0, errornr, eap->forceit); } -/* - * ":cnext", ":cnfile", ":cNext" and ":cprevious". - * ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". - * ":cdo", ":ldo", ":cfdo" and ":lfdo". - */ +/// ":cnext", ":cnfile", ":cNext" and ":cprevious". +/// ":lnext", ":lNext", ":lprevious", ":lnfile", ":lNfile" and ":lpfile". +/// ":cdo", ":ldo", ":cfdo" and ":lfdo". void ex_cnext(exarg_T *eap) { - qf_info_T *qi = &ql_info; + qf_info_T *qi; - if (eap->cmdidx == CMD_lnext - || eap->cmdidx == CMD_lNext - || eap->cmdidx == CMD_lprevious - || eap->cmdidx == CMD_lnfile - || eap->cmdidx == CMD_lNfile - || eap->cmdidx == CMD_lpfile - || eap->cmdidx == CMD_ldo - || eap->cmdidx == CMD_lfdo) { - qi = GET_LOC_LIST(curwin); - if (qi == NULL) { - EMSG(_(e_loclist)); - return; - } + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; } int errornr; @@ -3993,19 +4383,326 @@ void ex_cnext(exarg_T *eap) errornr = 1; } - qf_jump(qi, (eap->cmdidx == CMD_cnext || eap->cmdidx == CMD_lnext - || eap->cmdidx == CMD_cdo || eap->cmdidx == CMD_ldo) - ? FORWARD - : (eap->cmdidx == CMD_cnfile || eap->cmdidx == CMD_lnfile - || eap->cmdidx == CMD_cfdo || eap->cmdidx == CMD_lfdo) - ? FORWARD_FILE - : (eap->cmdidx == CMD_cpfile || eap->cmdidx == CMD_lpfile - || eap->cmdidx == CMD_cNfile || eap->cmdidx == CMD_lNfile) - ? BACKWARD_FILE - : BACKWARD, - errornr, eap->forceit); + // Depending on the command jump to either next or previous entry/file. + Direction dir; + switch (eap->cmdidx) { + case CMD_cprevious: + case CMD_lprevious: + case CMD_cNext: + case CMD_lNext: + dir = BACKWARD; + break; + case CMD_cnfile: + case CMD_lnfile: + case CMD_cfdo: + case CMD_lfdo: + dir = FORWARD_FILE; + break; + case CMD_cpfile: + case CMD_lpfile: + case CMD_cNfile: + case CMD_lNfile: + dir = BACKWARD_FILE; + break; + case CMD_cnext: + case CMD_lnext: + case CMD_cdo: + case CMD_ldo: + default: + dir = FORWARD; + break; + } + + qf_jump(qi, dir, errornr, eap->forceit); +} + +/// Find the first entry in the quickfix list 'qfl' from buffer 'bnr'. +/// The index of the entry is stored in 'errornr'. +/// Returns NULL if an entry is not found. +static qfline_T *qf_find_first_entry_in_buf(qf_list_T *qfl, + int bnr, + int *errornr) +{ + qfline_T *qfp = NULL; + int idx = 0; + + // Find the first entry in this file + FOR_ALL_QFL_ITEMS(qfl, qfp, idx) { + if (qfp->qf_fnum == bnr) { + break; + } + } + + *errornr = idx; + return qfp; +} + +/// Find the first quickfix entry on the same line as 'entry'. Updates 'errornr' +/// with the error number for the first entry. Assumes the entries are sorted in +/// the quickfix list by line number. +static qfline_T * qf_find_first_entry_on_line(qfline_T *entry, int *errornr) +{ + while (!got_int + && entry->qf_prev != NULL + && entry->qf_fnum == entry->qf_prev->qf_fnum + && entry->qf_lnum == entry->qf_prev->qf_lnum) { + entry = entry->qf_prev; + (*errornr)--; + } + + return entry; +} + +/// Find the last quickfix entry on the same line as 'entry'. Updates 'errornr' +/// with the error number for the last entry. Assumes the entries are sorted in +/// the quickfix list by line number. +static qfline_T * qf_find_last_entry_on_line(qfline_T *entry, int *errornr) +{ + while (!got_int + && entry->qf_next != NULL + && entry->qf_fnum == entry->qf_next->qf_fnum + && entry->qf_lnum == entry->qf_next->qf_lnum) { + entry = entry->qf_next; + (*errornr)++; + } + + return entry; +} + +/// Find the first quickfix entry below line 'lnum' in buffer 'bnr'. +/// 'qfp' points to the very first entry in the buffer and 'errornr' is the +/// index of the very first entry in the quickfix list. +/// Returns NULL if an entry is not found after 'lnum'. +static qfline_T *qf_find_entry_on_next_line(int bnr, + linenr_T lnum, + qfline_T *qfp, + int *errornr) +{ + if (qfp->qf_lnum > lnum) { + // First entry is after line 'lnum' + return qfp; + } + + // Find the entry just before or at the line 'lnum' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qfp->qf_next->qf_lnum <= lnum) { + qfp = qfp->qf_next; + (*errornr)++; + } + + if (qfp->qf_next == NULL || qfp->qf_next->qf_fnum != bnr) { + // No entries found after 'lnum' + return NULL; + } + + // Use the entry just after line 'lnum' + qfp = qfp->qf_next; + (*errornr)++; + + return qfp; +} + +/// Find the first quickfix entry before line 'lnum' in buffer 'bnr'. +/// 'qfp' points to the very first entry in the buffer and 'errornr' is the +/// index of the very first entry in the quickfix list. +/// Returns NULL if an entry is not found before 'lnum'. +static qfline_T *qf_find_entry_on_prev_line(int bnr, + linenr_T lnum, + qfline_T *qfp, + int *errornr) +{ + // Find the entry just before the line 'lnum' + while (qfp->qf_next != NULL + && qfp->qf_next->qf_fnum == bnr + && qfp->qf_next->qf_lnum < lnum) { + qfp = qfp->qf_next; + (*errornr)++; + } + + if (qfp->qf_lnum >= lnum) { // entry is after 'lnum' + return NULL; + } + + // If multiple entries are on the same line, then use the first entry + qfp = qf_find_first_entry_on_line(qfp, errornr); + + return qfp; +} + +/// Find a quickfix entry in 'qfl' closest to line 'lnum' in buffer 'bnr' in +/// the direction 'dir'. +static qfline_T *qf_find_closest_entry(qf_list_T *qfl, + int bnr, + linenr_T lnum, + int dir, + int *errornr) +{ + qfline_T *qfp; + + *errornr = 0; + + // Find the first entry in this file + qfp = qf_find_first_entry_in_buf(qfl, bnr, errornr); + if (qfp == NULL) { + return NULL; // no entry in this file + } + + if (dir == FORWARD) { + qfp = qf_find_entry_on_next_line(bnr, lnum, qfp, errornr); + } else { + qfp = qf_find_entry_on_prev_line(bnr, lnum, qfp, errornr); + } + + return qfp; +} + +/// Get the nth quickfix entry below the specified entry treating multiple +/// entries on a single line as one. Searches forward in the list. +static void qf_get_nth_below_entry(qfline_T *entry, + int *errornr, + linenr_T n) +{ + while (n-- > 0 && !got_int) { + qfline_T *first_entry = entry; + int first_errornr = *errornr; + + // Treat all the entries on the same line in this file as one + entry = qf_find_last_entry_on_line(entry, errornr); + + if (entry->qf_next == NULL + || entry->qf_next->qf_fnum != entry->qf_fnum) { + // If multiple entries are on the same line, then use the first + // entry + entry = first_entry; + *errornr = first_errornr; + break; + } + + entry = entry->qf_next; + (*errornr)++; + } +} + +/// Get the nth quickfix entry above the specified entry treating multiple +/// entries on a single line as one. Searches backwards in the list. +static void qf_get_nth_above_entry(qfline_T *entry, + int *errornr, + linenr_T n) +{ + while (n-- > 0 && !got_int) { + if (entry->qf_prev == NULL + || entry->qf_prev->qf_fnum != entry->qf_fnum) { + break; + } + + entry = entry->qf_prev; + (*errornr)--; + + // If multiple entries are on the same line, then use the first entry + entry = qf_find_first_entry_on_line(entry, errornr); + } +} + +/// Find the n'th quickfix entry adjacent to line 'lnum' in buffer 'bnr' in the +/// specified direction. +/// Returns the error number in the quickfix list or 0 if an entry is not found. +static int qf_find_nth_adj_entry(qf_list_T *qfl, + int bnr, + linenr_T lnum, + linenr_T n, + int dir) +{ + qfline_T *adj_entry; + int errornr; + + // Find an entry closest to the specified line + adj_entry = qf_find_closest_entry(qfl, bnr, lnum, dir, &errornr); + if (adj_entry == NULL) { + return 0; + } + + if (--n > 0) { + // Go to the n'th entry in the current buffer + if (dir == FORWARD) { + qf_get_nth_below_entry(adj_entry, &errornr, n); + } else { + qf_get_nth_above_entry(adj_entry, &errornr, n); + } + } + + return errornr; +} + +/// Jump to a quickfix entry in the current file nearest to the current line. +/// ":cabove", ":cbelow", ":labove" and ":lbelow" commands +void ex_cbelow(exarg_T *eap) +{ + qf_info_T *qi; + qf_list_T *qfl; + int dir; + int buf_has_flag; + int errornr = 0; + + if (eap->addr_count > 0 && eap->line2 <= 0) { + EMSG(_(e_invrange)); + return; + } + + // Check whether the current buffer has any quickfix entries + if (eap->cmdidx == CMD_cabove || eap->cmdidx == CMD_cbelow) { + buf_has_flag = BUF_HAS_QF_ENTRY; + } else { + buf_has_flag = BUF_HAS_LL_ENTRY; + } + if (!(curbuf->b_has_qf_entry & buf_has_flag)) { + EMSG(_(e_quickfix)); + return; + } + + if ((qi = qf_cmd_get_stack(eap, true)) == NULL) { + return; + } + + qfl = qf_get_curlist(qi); + // check if the list has valid errors + if (!qf_list_has_valid_entries(qfl)) { + EMSG(_(e_quickfix)); + return; + } + + if (eap->cmdidx == CMD_cbelow || eap->cmdidx == CMD_lbelow) { + dir = FORWARD; + } else { + dir = BACKWARD; + } + + errornr = qf_find_nth_adj_entry(qfl, curbuf->b_fnum, curwin->w_cursor.lnum, + eap->addr_count > 0 ? eap->line2 : 0, dir); + + if (errornr > 0) { + qf_jump(qi, 0, errornr, false); + } else { + EMSG(_(e_no_more_items)); + } +} + + +/// Return the autocmd name for the :cfile Ex commands +static char_u * cfile_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cfile: return (char_u *)"cfile"; + case CMD_cgetfile: return (char_u *)"cgetfile"; + case CMD_caddfile: return (char_u *)"caddfile"; + case CMD_lfile: return (char_u *)"lfile"; + case CMD_lgetfile: return (char_u *)"lgetfile"; + case CMD_laddfile: return (char_u *)"laddfile"; + default: return NULL; + } } + /* * ":cfile"/":cgetfile"/":caddfile" commands. * ":lfile"/":lgetfile"/":laddfile" commands. @@ -4016,28 +4713,25 @@ void ex_cfile(exarg_T *eap) qf_info_T *qi = &ql_info; char_u *au_name = NULL; - switch (eap->cmdidx) { - case CMD_cfile: au_name = (char_u *)"cfile"; break; - case CMD_cgetfile: au_name = (char_u *)"cgetfile"; break; - case CMD_caddfile: au_name = (char_u *)"caddfile"; break; - case CMD_lfile: au_name = (char_u *)"lfile"; break; - case CMD_lgetfile: au_name = (char_u *)"lgetfile"; break; - case CMD_laddfile: au_name = (char_u *)"laddfile"; break; - default: break; + au_name = cfile_get_auname(eap->cmdidx); + if (au_name != NULL + && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, false, curbuf)) { + if (aborting()) { + return; + } } - if (au_name != NULL) - apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, NULL, FALSE, curbuf); - if (*eap->arg != NUL) + if (*eap->arg != NUL) { set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); + } char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; - if (eap->cmdidx == CMD_lfile - || eap->cmdidx == CMD_lgetfile - || eap->cmdidx == CMD_laddfile) { + if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; } + incr_quickfix_busy(); + // This function is used by the :cfile, :cgetfile and :caddfile // commands. // :cfile always creates a new quickfix list and jumps to the @@ -4052,13 +4746,14 @@ void ex_cfile(exarg_T *eap) if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + decr_quickfix_busy(); return; } } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(qf_get_curlist(qi)); } - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf); } @@ -4069,6 +4764,8 @@ void ex_cfile(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + + decr_quickfix_busy(); } /// Return the vimgrep autocmd name. @@ -4189,8 +4886,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, // 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, + if (qf_add_entry(qf_get_curlist(qi), NULL, // dir fname, NULL, @@ -4203,8 +4899,8 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf, NULL, // search pattern 0, // nr 0, // type - true // valid - ) == FAIL) { + true) // valid + == QF_FAIL) { got_int = true; break; } @@ -4266,7 +4962,7 @@ void ex_vimgrep(exarg_T *eap) char_u *s; char_u *p; int fi; - qf_info_T *qi = &ql_info; + qf_list_T *qfl; win_T *wp = NULL; buf_T *buf; int duplicate_name = FALSE; @@ -4291,13 +4987,7 @@ void ex_vimgrep(exarg_T *eap) } } - if (eap->cmdidx == CMD_lgrep - || eap->cmdidx == CMD_lvimgrep - || eap->cmdidx == CMD_lgrepadd - || eap->cmdidx == CMD_lvimgrepadd) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; - } + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); if (eap->addr_count > 0) tomatch = eap->line2; @@ -4326,7 +5016,7 @@ void ex_vimgrep(exarg_T *eap) if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd) - || qi->qf_curlist == qi->qf_listcount) { + || qf_stack_empty(qi)) { // make place for a new list qf_new_list(qi, title); } @@ -4346,9 +5036,11 @@ void ex_vimgrep(exarg_T *eap) * ":lcd %:p:h" changes the meaning of short path names. */ os_dirname(dirname_start, MAXPATHL); + incr_quickfix_busy(); + // Remember the current quickfix list identifier, so that we can check for // autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; seconds = (time_t)0; for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { @@ -4377,9 +5069,10 @@ void ex_vimgrep(exarg_T *eap) // buffer above, autocommands might have changed the quickfix list. if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) { FreeWild(fcount, fnames); + decr_quickfix_busy(); goto theend; } - save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + save_qfid = qf_get_curlist(qi)->qf_id; if (buf == NULL) { if (!got_int) @@ -4447,10 +5140,11 @@ void ex_vimgrep(exarg_T *eap) FreeWild(fcount, fnames); - 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); + qfl = qf_get_curlist(qi); + qfl->qf_nonevalid = false; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); qf_update_buffer(qi, NULL); @@ -4460,16 +5154,14 @@ void ex_vimgrep(exarg_T *eap) // The QuickFixCmdPost autocmd may free the quickfix list. Check the list // is still valid. - if (!qflist_valid(wp, save_qfid)) { - goto theend; - } - - if (qf_restore_list(qi, save_qfid) == FAIL) { + if (!qflist_valid(wp, save_qfid) + || qf_restore_list(qi, save_qfid) == FAIL) { + decr_quickfix_busy(); goto theend; } // Jump to first match. - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { + if (!qf_list_empty(qf_get_curlist(qi))) { if ((flags & VGR_NOJUMP) == 0) { vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir); @@ -4477,6 +5169,8 @@ void ex_vimgrep(exarg_T *eap) } else EMSG2(_(e_nomatch2), s); + decr_quickfix_busy(); + /* If we loaded a dummy buffer into the current window, the autocommands * may have messed up things, need to redraw and recompute folds. */ if (redraw_for_dummy) { @@ -4653,15 +5347,62 @@ static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) } } +/// Copy the specified quickfix entry items into a new dict and appened the dict +/// to 'list'. Returns OK on success. +static int get_qfline_items(qfline_T *qfp, list_T *list) +{ + char_u buf[2]; + int bufnum; + + // Handle entries with a non-existing buffer number. + bufnum = qfp->qf_fnum; + if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) { + bufnum = 0; + } + + dict_T *const dict = tv_dict_alloc(); + tv_list_append_dict(list, dict); + + buf[0] = qfp->qf_type; + buf[1] = NUL; + if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL + || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) + == FAIL) + || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) == FAIL) + || (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 ? "" : (const char *)qfp->qf_pattern)) + == FAIL) + || (tv_dict_add_str( + dict, S_LEN("text"), + (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text)) + == FAIL) + || (tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL) + || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) + == FAIL)) { + // tv_dict_add* fail only if key already exist, but this is a newly + // allocated dictionary which is thus guaranteed to have no existing keys. + assert(false); + } + + return OK; +} + /// Add each quickfix error to list "list" as a dictionary. /// If qf_idx is -1, use the current list. Otherwise, use the specified list. -int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) +int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) { - const qf_info_T *qi = qi_arg; - char_u buf[2]; + qf_info_T *qi = qi_arg; + qf_list_T *qfl; qfline_T *qfp; int i; - int bufnum; if (qi == NULL) { qi = &ql_info; @@ -4677,56 +5418,19 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list) qf_idx = qi->qf_curlist; } - if (qf_idx >= qi->qf_listcount - || qi->qf_lists[qf_idx].qf_count == 0) { + if (qf_idx >= qi->qf_listcount) { return FAIL; } - qfp = qi->qf_lists[qf_idx].qf_start; - for (i = 1; !got_int && i <= qi->qf_lists[qf_idx].qf_count; i++) { - // Handle entries with a non-existing buffer number. - bufnum = qfp->qf_fnum; - if (bufnum != 0 && (buflist_findnr(bufnum) == NULL)) - bufnum = 0; - - dict_T *const dict = tv_dict_alloc(); - tv_list_append_dict(list, dict); - - buf[0] = qfp->qf_type; - buf[1] = NUL; - if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL - || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) - == FAIL) - || (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) - == FAIL) - || (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 - ? "" - : (const char *)qfp->qf_pattern)) == FAIL - || tv_dict_add_str(dict, S_LEN("text"), - (qfp->qf_text == NULL - ? "" - : (const char *)qfp->qf_text)) == FAIL - || tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL - || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) - == FAIL)) { - // tv_dict_add* fail only if key already exist, but this is a newly - // allocated dictionary which is thus guaranteed to have no existing keys. - assert(false); - } + qfl = qf_get_list(qi, qf_idx); + if (qf_list_empty(qfl)) { + return FAIL; + } - qfp = qfp->qf_next; - if (qfp == NULL) { - break; - } + FOR_ALL_QFL_ITEMS(qfl, qfp, i) { + get_qfline_items(qfp, list); } + return OK; } @@ -4742,7 +5446,8 @@ enum { QF_GETLIST_IDX = 0x40, QF_GETLIST_SIZE = 0x80, QF_GETLIST_TICK = 0x100, - QF_GETLIST_ALL = 0x1FF + QF_GETLIST_FILEWINID = 0x200, + QF_GETLIST_ALL = 0x3FF, }; /// Parse text from 'di' and return the quickfix list items. @@ -4766,12 +5471,12 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) } list_T *l = tv_list_alloc(kListLenMayKnow); - qf_info_T *const qi = ll_new_list(); + qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { (void)get_errorlist(qi, NULL, 0, l); - qf_free(qi, 0); + qf_free(&qi->qf_lists[0]); } xfree(qi); @@ -4798,12 +5503,17 @@ static int qf_winid(qf_info_T *qi) } /// Convert the keys in 'what' to quickfix list property flags. -static int qf_getprop_keys2flags(dict_T *what) +static int qf_getprop_keys2flags(const dict_T *what, bool loclist) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { int flags = QF_GETLIST_NONE; if (tv_dict_find(what, S_LEN("all")) != NULL) { flags |= QF_GETLIST_ALL; + if (!loclist) { + // File window ID is applicable only to location list windows + flags &= ~QF_GETLIST_FILEWINID; + } } if (tv_dict_find(what, S_LEN("title")) != NULL) { flags |= QF_GETLIST_TITLE; @@ -4832,6 +5542,9 @@ static int qf_getprop_keys2flags(dict_T *what) if (tv_dict_find(what, S_LEN("changedtick")) != NULL) { flags |= QF_GETLIST_TICK; } + if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) { + flags |= QF_GETLIST_FILEWINID; + } return flags; } @@ -4885,7 +5598,10 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what) } /// Return default values for quickfix list properties in retdict. -static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) +static int qf_getprop_defaults(qf_info_T *qi, + int flags, + int locstack, + dict_T *retdict) { int status = OK; @@ -4917,15 +5633,37 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) if ((status == OK) && (flags & QF_GETLIST_TICK)) { status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0); } + if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) { + status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); + } 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) +static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict) { return tv_dict_add_str(retdict, S_LEN("title"), - (const char *)qi->qf_lists[qf_idx].qf_title); + (const char *)qfl->qf_title); +} + +// Returns the identifier of the window used to display files from a location +// list. If there is no associated window, then returns 0. Useful only when +// called from a location list window. +static int qf_getprop_filewinid(const win_T *wp, const qf_info_T *qi, + dict_T *retdict) + FUNC_ATTR_NONNULL_ARG(3) +{ + handle_T winid = 0; + + if (wp != NULL && IS_LL_WINDOW(wp)) { + win_T *ll_wp = qf_find_win_with_loclist(qi); + if (ll_wp != NULL) { + winid = ll_wp->handle; + } + } + + return tv_dict_add_nr(retdict, S_LEN("filewinid"), winid); } /// Return the quickfix list items/entries as 'items' in retdict @@ -4939,13 +5677,13 @@ static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict) } /// 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) +static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict) { int status; - if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + if (qfl->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); + tv_copy(qfl->qf_ctx, &di->di_tv); status = tv_dict_add(retdict, di); if (status == FAIL) { tv_dict_item_free(di); @@ -4957,15 +5695,15 @@ static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict) 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) +/// Return the current quickfix list index as 'idx' in retdict +static int qf_getprop_idx(qf_list_T *qfl, 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; + int curidx = qfl->qf_index; + if (qf_list_empty(qfl)) { + // For empty lists, current index is set to 0 + curidx = 0; } - return tv_dict_add_nr(retdict, S_LEN("idx"), idx); + return tv_dict_add_nr(retdict, S_LEN("idx"), curidx); } /// Return quickfix/location list details (title) as a dictionary. @@ -4974,9 +5712,10 @@ static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict) int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + qf_list_T *qfl; dictitem_T *di = NULL; int status = OK; - int qf_idx; + int qf_idx = INVALID_QFIDX; if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) { return qf_get_list_from_lines(what, di, retdict); @@ -4986,19 +5725,21 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) qi = GET_LOC_LIST(wp); } - int flags = qf_getprop_keys2flags(what); + const int flags = qf_getprop_keys2flags(what, wp != NULL); - if (qi != NULL && qi->qf_listcount != 0) { + if (!qf_stack_empty(qi)) { qf_idx = qf_getprop_qfidx(qi, what); } // List is not present or is empty - if (qi == NULL || qi->qf_listcount == 0 || qf_idx == INVALID_QFIDX) { - return qf_getprop_defaults(qi, flags, retdict); + if (qf_stack_empty(qi) || qf_idx == INVALID_QFIDX) { + return qf_getprop_defaults(qi, flags, wp != NULL, retdict); } + qfl = qf_get_list(qi, qf_idx); + if (flags & QF_GETLIST_TITLE) { - status = qf_getprop_title(qi, qf_idx, retdict); + status = qf_getprop_title(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_NR)) { status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1); @@ -5010,33 +5751,37 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) status = qf_getprop_items(qi, qf_idx, retdict); } if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { - status = qf_getprop_ctx(qi, qf_idx, retdict); + status = qf_getprop_ctx(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_ID)) { - status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id); + status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id); } if ((status == OK) && (flags & QF_GETLIST_IDX)) { - status = qf_getprop_idx(qi, qf_idx, retdict); + status = qf_getprop_idx(qfl, retdict); } if ((status == OK) && (flags & QF_GETLIST_SIZE)) { status = tv_dict_add_nr(retdict, S_LEN("size"), - qi->qf_lists[qf_idx].qf_count); + qfl->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); + qfl->qf_changedtick); + } + if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { + status = qf_getprop_filewinid(wp, qi, retdict); } return status; } -// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the -// items in the dict 'd'. +/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the +/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry' +/// to true. static int qf_add_entry_from_dict( - qf_info_T *qi, - int qf_idx, + qf_list_T *qfl, const dict_T *d, - bool first_entry) + bool first_entry, + bool *valid_entry) FUNC_ATTR_NONNULL_ALL { static bool did_bufnr_emsg; @@ -5080,17 +5825,16 @@ static int qf_add_entry_from_dict( valid = tv_dict_get_number(d, "valid"); } - const int status = qf_add_entry(qi, - qf_idx, - NULL, // dir + const int status = qf_add_entry(qfl, + NULL, // dir (char_u *)filename, (char_u *)module, bufnum, (char_u *)text, lnum, col, - vcol, // vis_col - (char_u *)pattern, // search pattern + vcol, // vis_col + (char_u *)pattern, // search pattern nr, (char_u)(type == NULL ? NUL : *type), valid); @@ -5100,6 +5844,10 @@ static int qf_add_entry_from_dict( xfree(pattern); xfree(text); + if (valid) { + *valid_entry = true; + } + return status; } @@ -5108,19 +5856,22 @@ static int qf_add_entry_from_dict( static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char_u *title, int action) { + qf_list_T *qfl = qf_get_list(qi, qf_idx); qfline_T *old_last = NULL; int retval = OK; + bool valid_entry = false; if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list qf_new_list(qi, title); qf_idx = qi->qf_curlist; - } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { + qfl = qf_get_list(qi, qf_idx); + } else if (action == 'a' && !qf_list_empty(qfl)) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qf_idx].qf_last; + old_last = qfl->qf_last; } else if (action == 'r') { - qf_free_items(qi, qf_idx); - qf_store_title(qi, qf_idx, title); + qf_free_items(qfl); + qf_store_title(qfl, title); } TV_LIST_ITER_CONST(list, li, { @@ -5133,23 +5884,30 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, continue; } - retval = qf_add_entry_from_dict(qi, qf_idx, d, li == tv_list_first(list)); - if (retval == FAIL) { + retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list), + &valid_entry); + if (retval == QF_FAIL) { break; } }); - if (qi->qf_lists[qf_idx].qf_index == 0) { - // no valid entry - qi->qf_lists[qf_idx].qf_nonevalid = true; - } else { - qi->qf_lists[qf_idx].qf_nonevalid = false; + // Check if any valid error entries are added to the list. + if (valid_entry) { + qfl->qf_nonevalid = false; + } else if (qfl->qf_index == 0) { + qfl->qf_nonevalid = true; } + + // If not appending to the list, set the current error to the first entry if (action != 'a') { - qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; - if (qi->qf_lists[qf_idx].qf_count > 0) { - qi->qf_lists[qf_idx].qf_index = 1; - } + qfl->qf_ptr = qfl->qf_start; + } + + // Update the current error index if not appending to the list or if the + // list was empty before and it is not empty now. + if ((action != 'a' || qfl->qf_index == 0) + && !qf_list_empty(qfl)) { + qfl->qf_index = 1; } // Don't update the cursor in quickfix window when appending entries @@ -5158,7 +5916,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, return retval; } -// Get the quickfix list index from 'nr' or 'id' +/// Get the quickfix list index from 'nr' or 'id' static int qf_setprop_get_qfidx( const qf_info_T *qi, const dict_T *what, @@ -5182,7 +5940,7 @@ static int qf_setprop_get_qfidx( // non-available list and add the new list at the end of the // stack. *newlist = true; - qf_idx = qi->qf_listcount > 0 ? qi->qf_listcount - 1 : 0; + qf_idx = qf_stack_empty(qi) ? 0 : qi->qf_listcount - 1; } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return INVALID_QFIDX; } else if (action != ' ') { @@ -5190,7 +5948,7 @@ static int qf_setprop_get_qfidx( } } else if (di->di_tv.v_type == VAR_STRING && strequal((const char *)di->di_tv.vval.v_string, "$")) { - if (qi->qf_listcount > 0) { + if (!qf_stack_empty(qi)) { qf_idx = qi->qf_listcount - 1; } else if (*newlist) { qf_idx = 0; @@ -5218,13 +5976,13 @@ static int qf_setprop_title(qf_info_T *qi, int qf_idx, const dict_T *what, const dictitem_T *di) FUNC_ATTR_NONNULL_ALL { + qf_list_T *qfl = qf_get_list(qi, qf_idx); if (di->di_tv.v_type != VAR_STRING) { return FAIL; } - xfree(qi->qf_lists[qf_idx].qf_title); - qi->qf_lists[qf_idx].qf_title = - (char_u *)tv_dict_get_string(what, "title", true); + xfree(qfl->qf_title); + qfl->qf_title = (char_u *)tv_dict_get_string(what, "title", true); if (qf_idx == qi->qf_curlist) { qf_update_win_titlevar(qi); } @@ -5278,7 +6036,7 @@ static int qf_setprop_items_from_lines( } if (action == 'r') { - qf_free_items(qi, qf_idx); + qf_free_items(&qi->qf_lists[qf_idx]); } if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat, false, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { @@ -5289,27 +6047,28 @@ static int qf_setprop_items_from_lines( } // Set quickfix list context. -static int qf_setprop_context(qf_info_T *qi, int qf_idx, dictitem_T *di) +static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di) FUNC_ATTR_NONNULL_ALL { - tv_free(qi->qf_lists[qf_idx].qf_ctx); + tv_free(qfl->qf_ctx); typval_T *ctx = xcalloc(1, sizeof(typval_T)); tv_copy(&di->di_tv, ctx); - qi->qf_lists[qf_idx].qf_ctx = ctx; + qfl->qf_ctx = ctx; return OK; } -// Set quickfix/location list properties (title, items, context). -// Also used to add items from parsing a list of lines. -// Used by the setqflist() and setloclist() Vim script functions. +/// Set quickfix/location list properties (title, items, context). +/// Also used to add items from parsing a list of lines. +/// Used by the setqflist() and setloclist() Vim script functions. static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char_u *title) FUNC_ATTR_NONNULL_ALL { + qf_list_T *qfl; dictitem_T *di; int retval = FAIL; - bool newlist = action == ' ' || qi->qf_curlist == qi->qf_listcount; + bool newlist = action == ' ' || qf_stack_empty(qi); int qf_idx = qf_setprop_get_qfidx(qi, what, action, &newlist); if (qf_idx == INVALID_QFIDX) { // List not found return FAIL; @@ -5321,6 +6080,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, qf_idx = qi->qf_curlist; } + qfl = qf_get_list(qi, qf_idx); if ((di = tv_dict_find(what, S_LEN("title"))) != NULL) { retval = qf_setprop_title(qi, qf_idx, what, di); } @@ -5331,17 +6091,18 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, retval = qf_setprop_items_from_lines(qi, qf_idx, what, di, action); } if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { - retval = qf_setprop_context(qi, qf_idx, di); + retval = qf_setprop_context(qfl, di); } if (retval == OK) { - qf_list_changed(qi, qf_idx); + qf_list_changed(qfl); } return retval; } -// Find the non-location list window with the specified location list. +/// Find the non-location list window with the specified location list stack in +/// the current tabpage. static win_T * find_win_with_ll(qf_info_T *qi) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -5362,7 +6123,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) if (qfwin != NULL) { // If the quickfix/location list window is open, then clear it if (qi->qf_curlist < qi->qf_listcount) { - qf_free(qi, qi->qf_curlist); + qf_free(qf_get_curlist(qi)); } qf_update_buffer(qi, NULL); } @@ -5386,15 +6147,14 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) } else if (IS_LL_WINDOW(orig_wp)) { // If the location list window is open, then create a new empty location // list - qf_info_T *new_ll = ll_new_list(); + qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); // first free the list reference in the location list window ll_free_all(&orig_wp->w_llist_ref); orig_wp->w_llist_ref = new_ll; if (llwin != NULL) { - llwin->w_llist = new_ll; - new_ll->qf_refcount++; + win_set_loclist(wp, new_ll); } } } @@ -5415,15 +6175,22 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, if (action == 'f') { // Free the entire quickfix or location list stack qf_free_stack(wp, qi); - } else if (what != NULL) { + return OK; + } + + incr_quickfix_busy(); + + if (what != NULL) { 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); + qf_list_changed(qf_get_curlist(qi)); } } + decr_quickfix_busy(); + return retval; } @@ -5474,6 +6241,62 @@ bool set_ref_in_quickfix(int copyID) return abort; } +/// Return the autocmd name for the :cbuffer Ex commands +static char_u * cbuffer_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cbuffer: return (char_u *)"cbuffer"; + case CMD_cgetbuffer: return (char_u *)"cgetbuffer"; + case CMD_caddbuffer: return (char_u *)"caddbuffer"; + case CMD_lbuffer: return (char_u *)"lbuffer"; + case CMD_lgetbuffer: return (char_u *)"lgetbuffer"; + case CMD_laddbuffer: return (char_u *)"laddbuffer"; + default: return NULL; + } +} + +/// Process and validate the arguments passed to the :cbuffer, :caddbuffer, +/// :cgetbuffer, :lbuffer, :laddbuffer, :lgetbuffer Ex commands. +static int cbuffer_process_args(exarg_T *eap, + buf_T **bufp, + linenr_T *line1, + linenr_T *line2) +{ + buf_T *buf = NULL; + + if (*eap->arg == NUL) + buf = curbuf; + else if (*skipwhite(skipdigits(eap->arg)) == NUL) + buf = buflist_findnr(atoi((char *)eap->arg)); + + if (buf == NULL) { + EMSG(_(e_invarg)); + return FAIL; + } + + if (buf->b_ml.ml_mfp == NULL) { + EMSG(_("E681: Buffer is not loaded")); + return FAIL; + } + + if (eap->addr_count == 0) { + eap->line1 = 1; + 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) { + EMSG(_(e_invrange)); + return FAIL; + } + + *line1 = eap->line1; + *line2 = eap->line2; + *bufp = buf; + + return OK; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. @@ -5485,34 +6308,14 @@ bool set_ref_in_quickfix(int copyID) void ex_cbuffer(exarg_T *eap) { buf_T *buf = NULL; - qf_info_T *qi = &ql_info; - const char *au_name = NULL; + char_u *au_name = NULL; win_T *wp = NULL; + char_u *qf_title; + linenr_T line1; + linenr_T line2; - switch (eap->cmdidx) { - case CMD_cbuffer: - au_name = "cbuffer"; - break; - case CMD_cgetbuffer: - au_name = "cgetbuffer"; - break; - case CMD_caddbuffer: - au_name = "caddbuffer"; - break; - case CMD_lbuffer: - au_name = "lbuffer"; - break; - case CMD_lgetbuffer: - au_name = "lgetbuffer"; - break; - case CMD_laddbuffer: - au_name = "laddbuffer"; - break; - default: - break; - } - - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, + au_name = cbuffer_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { return; @@ -5520,114 +6323,87 @@ void ex_cbuffer(exarg_T *eap) } // Must come after autocommands. - if (eap->cmdidx == CMD_lbuffer - || eap->cmdidx == CMD_lgetbuffer - || eap->cmdidx == CMD_laddbuffer) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); + + if (cbuffer_process_args(eap, &buf, &line1, &line2) == FAIL) { + return; } - if (*eap->arg == NUL) - buf = curbuf; - else if (*skipwhite(skipdigits(eap->arg)) == NUL) - buf = buflist_findnr(atoi((char *)eap->arg)); - if (buf == NULL) - EMSG(_(e_invarg)); - else if (buf->b_ml.ml_mfp == NULL) - EMSG(_("E681: Buffer is not loaded")); - else { - if (eap->addr_count == 0) { - eap->line1 = 1; - 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) { - EMSG(_(e_invrange)); - } else { - char_u *qf_title = qf_cmdtitle(*eap->cmdlinep); + qf_title = qf_cmdtitle(*eap->cmdlinep); - if (buf->b_sfname) { - vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", - (char *)qf_title, (char *)buf->b_sfname); - qf_title = IObuff; - } + if (buf->b_sfname) { + vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", + (char *)qf_title, (char *)buf->b_sfname); + qf_title = IObuff; + } - 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); - } - // Remember the current quickfix list identifier, so that we can - // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; - if (au_name != NULL) { - const buf_T *const curbuf_old = curbuf; - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, - curbuf->b_fname, true, curbuf); - if (curbuf != curbuf_old) { - // Autocommands changed buffer, don't jump now, "qi" may - // be invalid. - res = 0; - } - } - // Jump to the first error for new list and if autocmds didn't - // free the list. - if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) - && qflist_valid(wp, save_qfid)) { - // display the first error - qf_jump_first(qi, save_qfid, eap->forceit); - } + incr_quickfix_busy(); + + 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 (qf_stack_empty(qi)) { + decr_quickfix_busy(); + return; + } + if (res >= 0) { + qf_list_changed(qf_get_curlist(qi)); + } + // Remember the current quickfix list identifier, so that we can + // check for autocommands changing the current quickfix list. + unsigned save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) { + const buf_T *const curbuf_old = curbuf; + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, + curbuf->b_fname, true, curbuf); + if (curbuf != curbuf_old) { + // Autocommands changed buffer, don't jump now, "qi" may + // be invalid. + res = 0; } } + // Jump to the first error for new list and if autocmds didn't + // free the list. + if (res > 0 && (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) + && qflist_valid(wp, save_qfid)) { + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + } + + decr_quickfix_busy(); } -/* - * ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. - * ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. - */ +/// Return the autocmd name for the :cexpr Ex commands. +static char_u * cexpr_get_auname(cmdidx_T cmdidx) +{ + switch (cmdidx) { + case CMD_cexpr: return (char_u *)"cexpr"; + case CMD_cgetexpr: return (char_u *)"cgetexpr"; + case CMD_caddexpr: return (char_u *)"caddexpr"; + case CMD_lexpr: return (char_u *)"lexpr"; + case CMD_lgetexpr: return (char_u *)"lgetexpr"; + case CMD_laddexpr: return (char_u *)"laddexpr"; + default: return NULL; + } +} + +/// ":cexpr {expr}", ":cgetexpr {expr}", ":caddexpr {expr}" command. +/// ":lexpr {expr}", ":lgetexpr {expr}", ":laddexpr {expr}" command. void ex_cexpr(exarg_T *eap) { - qf_info_T *qi = &ql_info; - const char *au_name = NULL; + char_u *au_name = NULL; win_T *wp = NULL; - switch (eap->cmdidx) { - case CMD_cexpr: - au_name = "cexpr"; - break; - case CMD_cgetexpr: - au_name = "cgetexpr"; - break; - case CMD_caddexpr: - au_name = "caddexpr"; - break; - case CMD_lexpr: - au_name = "lexpr"; - break; - case CMD_lgetexpr: - au_name = "lgetexpr"; - break; - case CMD_laddexpr: - au_name = "laddexpr"; - break; - default: - break; - } - if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, (char_u *)au_name, + au_name = cexpr_get_auname(eap->cmdidx); + if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name, curbuf->b_fname, true, curbuf)) { if (aborting()) { return; } } - if (eap->cmdidx == CMD_lexpr - || eap->cmdidx == CMD_lgetexpr - || eap->cmdidx == CMD_laddexpr) { - qi = ll_get_or_alloc_list(curwin); - wp = curwin; - } + qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp); /* Evaluate the expression. When the result is a string or a list we can * use it to fill the errorlist. */ @@ -5635,19 +6411,24 @@ void ex_cexpr(exarg_T *eap) if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) || tv.v_type == VAR_LIST) { + incr_quickfix_busy(); 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 (qf_stack_empty(qi)) { + decr_quickfix_busy(); + goto cleanup; + } if (res >= 0) { - qf_list_changed(qi, qi->qf_curlist); + qf_list_changed(qf_get_curlist(qi)); } // Remember the current quickfix list identifier, so that we can // check for autocommands changing the current quickfix list. - unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; + unsigned save_qfid = qf_get_curlist(qi)->qf_id; if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); } // Jump to the first error for a new list and if autocmds didn't @@ -5658,9 +6439,11 @@ void ex_cexpr(exarg_T *eap) // display the first error qf_jump_first(qi, save_qfid, eap->forceit); } + decr_quickfix_busy(); } else { EMSG(_("E777: String or List expected")); } +cleanup: tv_clear(&tv); } } @@ -5687,7 +6470,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll) } if (qi == NULL) { // Allocate a new location list for help text matches - qi = ll_new_list(); + qi = qf_alloc_stack(QFLT_LOCATION); *new_ll = true; } @@ -5718,8 +6501,7 @@ static void hgr_search_file( line[--l] = NUL; } - if (qf_add_entry(qi, - qi->qf_curlist, + if (qf_add_entry(qf_get_curlist(qi), NULL, // dir fname, NULL, @@ -5731,8 +6513,8 @@ static void hgr_search_file( NULL, // search pattern 0, // nr 1, // type - true // valid - ) == FAIL) { + true) // valid + == QF_FAIL) { got_int = true; if (line != IObuff) { xfree(line); @@ -5823,10 +6605,12 @@ void ex_helpgrep(exarg_T *eap) char_u *const save_cpo = p_cpo; p_cpo = empty_option; - if (eap->cmdidx == CMD_lhelpgrep) { + if (is_loclist_cmd(eap->cmdidx)) { qi = hgr_get_ll(&new_qi); } + incr_quickfix_busy(); + // Check for a specified language char_u *const lang = check_help_lang(eap->arg); regmatch_T regmatch = { @@ -5841,10 +6625,12 @@ void ex_helpgrep(exarg_T *eap) vim_regfree(regmatch.regprog); - 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_T *qfl = qf_get_curlist(qi); + qfl->qf_nonevalid = false; + qfl->qf_ptr = qfl->qf_start; + qfl->qf_index = 1; + qf_list_changed(qfl); + qf_update_buffer(qi, NULL); } if (p_cpo == empty_option) { @@ -5854,23 +6640,24 @@ void ex_helpgrep(exarg_T *eap) 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 && IS_LL_STACK(qi) && qf_find_buf(qi) == NULL) { // autocommands made "qi" invalid + decr_quickfix_busy(); return; } } - /* Jump to first match. */ - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) - qf_jump(qi, 0, 0, FALSE); - else + // Jump to first match. + if (!qf_list_empty(qf_get_curlist(qi))) { + qf_jump(qi, 0, 0, false); + } else { EMSG2(_(e_nomatch2), eap->arg); + } + + decr_quickfix_busy(); if (eap->cmdidx == CMD_lhelpgrep) { // If the help window is not opened or if it already points to the @@ -5885,3 +6672,4 @@ void ex_helpgrep(exarg_T *eap) } } + diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 1ce0b5217e..7b9601a5a6 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -286,6 +286,11 @@ int update_screen(int type) return FAIL; } + // May have postponed updating diffs. + if (need_diff_redraw) { + diff_redraw(true); + } + if (must_redraw) { if (type < must_redraw) /* use maximal type */ type = must_redraw; diff --git a/src/nvim/search.c b/src/nvim/search.c index fb31e76986..a298f7333e 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -516,19 +516,17 @@ void last_pat_prog(regmmatch_T *regmatch) /// the index of the first matching /// subpattern plus one; one if there was none. int searchit( - win_T *win, /* window to search in, can be NULL for a - buffer without a window! */ + win_T *win, // window to search in; can be NULL for a + // buffer without a window! buf_T *buf, pos_T *pos, - pos_T *end_pos, // set to end of the match, unless NULL + pos_T *end_pos, // set to end of the match, unless NULL Direction dir, char_u *pat, long count, int options, - int pat_use, // which pattern to use when "pat" is empty - linenr_T stop_lnum, // stop after this line number when != 0 - proftime_T *tm, // timeout limit or NULL - int *timed_out // set when timed out or NULL + int pat_use, // which pattern to use when "pat" is empty + searchit_arg_T *extra_arg // optional extra arguments, can be NULL ) { int found; @@ -548,7 +546,16 @@ int searchit( int submatch = 0; bool first_match = true; int save_called_emsg = called_emsg; - int break_loop = FALSE; + int break_loop = false; + linenr_T stop_lnum = 0; // stop after this line number when != 0 + proftime_T *tm = NULL; // timeout limit or NULL + int *timed_out = NULL; // set when timed out or NULL + + if (extra_arg != NULL) { + stop_lnum = extra_arg->sa_stop_lnum; + tm = extra_arg->sa_tm; + timed_out = &extra_arg->sa_timed_out; + } if (search_regcomp(pat, RE_SEARCH, pat_use, (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { @@ -889,6 +896,9 @@ int searchit( give_warning((char_u *)_(dir == BACKWARD ? top_bot_msg : bot_top_msg), true); } + if (extra_arg != NULL) { + extra_arg->sa_wrapped = true; + } } if (got_int || called_emsg || (timed_out != NULL && *timed_out) @@ -983,8 +993,7 @@ int do_search( char_u *pat, long count, int options, - proftime_T *tm, // timeout limit or NULL - int *timed_out // flag set on timeout or NULL + searchit_arg_T *sia // optional arguments or NULL ) { pos_T pos; /* position of the last match */ @@ -1271,7 +1280,7 @@ int do_search( & (SEARCH_KEEP + SEARCH_PEEK + SEARCH_HIS + SEARCH_MSG + SEARCH_START + ((pat != NULL && *pat == ';') ? 0 : SEARCH_NOOF)))), - RE_LAST, (linenr_T)0, tm, timed_out); + RE_LAST, sia); if (dircp != NULL) { *dircp = dirc; // restore second '/' or '?' for normal_cmd() @@ -3799,8 +3808,9 @@ current_quote( } vis_bef_curs = lt(VIsual, curwin->w_cursor); + vis_empty = equalpos(VIsual, curwin->w_cursor); if (*p_sel == 'e') { - if (!vis_bef_curs) { + if (!vis_bef_curs && !vis_empty) { // VIsual needs to be start of Visual selection. pos_T t = curwin->w_cursor; @@ -3810,8 +3820,8 @@ current_quote( restore_vis_bef = true; } dec_cursor(); + vis_empty = equalpos(VIsual, curwin->w_cursor); } - vis_empty = equalpos(VIsual, curwin->w_cursor); } if (!vis_empty) { @@ -4028,9 +4038,6 @@ current_search( bool old_p_ws = p_ws; pos_T save_VIsual = VIsual; - /* wrapping should not occur */ - p_ws = false; - /* Correct cursor when 'selection' is exclusive */ if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) dec_cursor(); @@ -4040,25 +4047,21 @@ current_search( pos_T pos; // position after the pattern int result; // result of various function calls + orig_pos = pos = curwin->w_cursor; if (VIsual_active) { - orig_pos = pos = curwin->w_cursor; - // Searching further will extend the match. if (forward) { incl(&pos); } else { decl(&pos); } - } else { - orig_pos = pos = curwin->w_cursor; } // Is the pattern is zero-width?, this time, don't care about the direction - int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor, - FORWARD); - if (one_char == -1) { - p_ws = old_p_ws; - return FAIL; /* pattern not found */ + int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, + FORWARD); + if (zero_width == -1) { + return FAIL; // pattern not found } /* @@ -4070,15 +4073,22 @@ current_search( int dir = forward ? i : !i; int flags = 0; - if (!dir && !one_char) { + if (!dir && !zero_width) { flags = SEARCH_END; } end_pos = pos; + // wrapping should not occur in the first round + if (i == 0) { + p_ws = false; + } + result = searchit(curwin, curbuf, &pos, &end_pos, (dir ? FORWARD : BACKWARD), spats[last_idx].pat, i ? count : 1, - SEARCH_KEEP | flags, RE_SEARCH, 0, NULL, NULL); + SEARCH_KEEP | flags, RE_SEARCH, NULL); + + p_ws = old_p_ws; // First search may fail, but then start searching from the // beginning of the file (cursor might be on the search match) @@ -4088,7 +4098,6 @@ current_search( curwin->w_cursor = orig_pos; if (VIsual_active) VIsual = save_VIsual; - p_ws = old_p_ws; return FAIL; } else if (i == 0 && !result) { if (forward) { // try again from start of buffer @@ -4100,26 +4109,24 @@ current_search( ml_get(curwin->w_buffer->b_ml.ml_line_count)); } } - p_ws = old_p_ws; } pos_T start_pos = pos; - p_ws = old_p_ws; - if (!VIsual_active) { VIsual = start_pos; } // put cursor on last character of match curwin->w_cursor = end_pos; - if (lt(VIsual, end_pos)) { + if (lt(VIsual, end_pos) && forward) { dec_cursor(); + } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) { + curwin->w_cursor = pos; // put the cursor on the start of the match } VIsual_active = true; VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // Update the inversion. if (*p_sel == 'e') { // Correction for exclusive selection depends on the direction. if (forward && ltoreq(VIsual, curwin->w_cursor)) { @@ -4141,13 +4148,13 @@ current_search( return OK; } -/// Check if the pattern is one character long or zero-width. +/// Check if the pattern is zero-width. /// If move is true, check from the beginning of the buffer, /// else from position "cur". /// "direction" is FORWARD or BACKWARD. /// Returns TRUE, FALSE or -1 for failure. -static int is_one_char(char_u *pattern, bool move, pos_T *cur, - Direction direction) +static int +is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) { regmmatch_T regmatch; int nmatched = 0; @@ -4175,7 +4182,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, flag = SEARCH_START; } if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1, - SEARCH_KEEP + flag, RE_SEARCH, 0, NULL, NULL) != FAIL) { + SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if // start and end are in the same position. called_emsg = false; @@ -4195,13 +4202,6 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur, result = (nmatched != 0 && regmatch.startpos[0].lnum == regmatch.endpos[0].lnum && regmatch.startpos[0].col == regmatch.endpos[0].col); - // one char width - if (!result - && nmatched != 0 - && inc(&pos) >= 0 - && pos.col == regmatch.endpos[0].col) { - result = true; - } } } @@ -4265,7 +4265,7 @@ static void search_stat(int dirc, pos_T *pos, start = profile_setlimit(20L); while (!got_int && searchit(curwin, curbuf, &lastpos, NULL, FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST, - (linenr_T)0, NULL, NULL) != FAIL) { + NULL) != FAIL) { // Stop after passing the time limit. if (profile_passed_limit(start)) { cnt = OUT_OF_TIME; diff --git a/src/nvim/search.h b/src/nvim/search.h index cb094aab8c..0366aee8a1 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -70,6 +70,15 @@ typedef struct spat { dict_T *additional_data; ///< Additional data from ShaDa file. } SearchPattern; +/// Optional extra arguments for searchit(). +typedef struct { + linenr_T sa_stop_lnum; ///< stop after this line number when != 0 + proftime_T *sa_tm; ///< timeout limit or NULL + int sa_timed_out; ///< set when timed out + int sa_wrapped; ///< search wrapped around +} searchit_arg_T; + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.h.generated.h" #endif diff --git a/src/nvim/spell.c b/src/nvim/spell.c index a3c1746383..5feb7efda9 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1910,11 +1910,11 @@ int init_syl_tab(slang_T *slang) // Count the number of syllables in "word". // When "word" contains spaces the syllables after the last space are counted. // Returns zero if syllables are not defines. -static int count_syllables(slang_T *slang, char_u *word) +static int count_syllables(slang_T *slang, const char_u *word) + FUNC_ATTR_NONNULL_ALL { int cnt = 0; bool skip = false; - char_u *p; int len; syl_item_T *syl; int c; @@ -1922,7 +1922,7 @@ static int count_syllables(slang_T *slang, char_u *word) if (slang->sl_syllable == NULL) return 0; - for (p = word; *p != NUL; p += len) { + for (const char_u *p = word; *p != NUL; p += len) { // When running into a space reset counter. if (*p == ' ') { len = 1; @@ -2625,9 +2625,10 @@ static bool spell_mb_isword_class(int cl, const win_T *wp) // Returns true if "p" points to a word character. // Wide version of spell_iswordp(). -static bool spell_iswordp_w(int *p, win_T *wp) +static bool spell_iswordp_w(const int *p, const win_T *wp) + FUNC_ATTR_NONNULL_ALL { - int *s; + const int *s; if (*p < 256 ? wp->w_s->b_spell_ismw[*p] : (wp->w_s->b_spell_ismw_mb != NULL @@ -3031,7 +3032,7 @@ void ex_spellrepall(exarg_T *eap) sub_nlines = 0; curwin->w_cursor.lnum = 0; while (!got_int) { - if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL, NULL) == 0 + if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL) == 0 || u_save_cursor() == FAIL) { break; } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index eeec5be120..4fac001bc5 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -265,6 +265,8 @@ // follow; never used in prefix tree #define BY_SPECIAL BY_FLAGS2 // highest special byte value +#define ZERO_FLAG 65009 // used when flag is zero: "0" + // Flags used in .spl file for soundsalike flags. #define SAL_F0LLOWUP 1 #define SAL_COLLAPSE 2 @@ -2783,6 +2785,7 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum } // Get one affix name from "*pp" and advance the pointer. +// Returns ZERO_FLAG for "0". // Returns zero for an error, still advances the pointer then. static unsigned get_affitem(int flagtype, char_u **pp) { @@ -2794,6 +2797,9 @@ static unsigned get_affitem(int flagtype, char_u **pp) return 0; } res = getdigits_int(pp, true, 0); + if (res == 0) { + res = ZERO_FLAG; + } } else { res = mb_ptr2char_adv((const char_u **)pp); if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG @@ -2915,10 +2921,15 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) int digits = getdigits_int(&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; - if (n == flag) + if (n == 0) { + n = ZERO_FLAG; + } + if (n == flag) { return true; - if (*p != NUL) // skip over comma - ++p; + } + if (*p != NUL) { // skip over comma + p++; + } } break; } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ac8ace9fff..bdbc09a87a 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -2460,11 +2460,8 @@ update_si_end( int force /* when TRUE overrule a previous end */ ) { - lpos_T startpos; - lpos_T endpos; lpos_T hl_endpos; lpos_T end_endpos; - int end_idx; /* return quickly for a keyword */ if (sip->si_idx < 0) @@ -2480,9 +2477,12 @@ update_si_end( * We need to find the end of the region. It may continue in the next * line. */ - end_idx = 0; - startpos.lnum = current_lnum; - startpos.col = startcol; + int end_idx = 0; + lpos_T startpos = { + .lnum = current_lnum, + .col = startcol, + }; + lpos_T endpos = { 0 }; find_endpos(sip->si_idx, &startpos, &endpos, &hl_endpos, &(sip->si_flags), &end_endpos, &end_idx, sip->si_extmatch); diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 0d42deed2b..9e8c05fb1e 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -65,6 +65,7 @@ typedef struct tag_pointers { char_u *tagkind_end; // end of tagkind char_u *user_data; // user_data string char_u *user_data_end; // end of user_data + linenr_T tagline; // "line:" value } tagptrs_T; /* @@ -988,9 +989,7 @@ add_llist_tags( cmd[len] = NUL; } - if ((dict = tv_dict_alloc()) == NULL) { - continue; - } + dict = tv_dict_alloc(); tv_list_append_dict(list, dict); tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); @@ -2547,6 +2546,7 @@ parse_match( tagp->tagkind = NULL; tagp->user_data = NULL; + tagp->tagline = 0; tagp->command_end = NULL; if (retval == OK) { @@ -2566,6 +2566,8 @@ parse_match( tagp->tagkind = p + 5; } else if (STRNCMP(p, "user_data:", 10) == 0) { tagp->user_data = p + 10; + } else if (STRNCMP(p, "line:", 5) == 0) { + tagp->tagline = atoi((char *)p + 5); } if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; @@ -2813,9 +2815,15 @@ static int jumpto_tag( p_ic = FALSE; /* don't ignore case now */ p_scs = FALSE; save_lnum = curwin->w_cursor.lnum; - curwin->w_cursor.lnum = 0; /* start search before first line */ + if (tagp.tagline > 0) { + // start search before line from "line:" field + curwin->w_cursor.lnum = tagp.tagline - 1; + } else { + // start search before first line + curwin->w_cursor.lnum = 0; + } if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, - search_options, NULL, NULL)) { + search_options, NULL)) { retval = OK; } else { int found = 1; @@ -2826,20 +2834,18 @@ static int jumpto_tag( */ p_ic = TRUE; if (!do_search(NULL, pbuf[0], pbuf + 1, (long)1, - search_options, NULL, NULL)) { + search_options, NULL)) { // Failed to find pattern, take a guess: "^func (" found = 2; (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, - search_options, NULL, NULL)) { + if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', pbuf, (long)1, - search_options, NULL, NULL)) { + if (!do_search(NULL, '/', pbuf, (long)1, search_options, NULL)) { found = 0; } } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 7609006906..c5e756905a 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -138,6 +138,8 @@ struct terminal { int pressed_button; // which mouse button is pressed bool pending_resize; // pending width/height + bool color_set[16]; + size_t refcount; // reference count }; @@ -241,6 +243,7 @@ Terminal *terminal_open(TerminalOptions opts) (uint8_t)((color_val >> 8) & 0xFF), (uint8_t)((color_val >> 0) & 0xFF)); vterm_state_set_palette_color(state, i, &color); + rv->color_set[i] = true; } } } @@ -598,16 +601,22 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int vt_fg = fg_default ? -1 : get_rgb(state, cell.fg); int vt_bg = bg_default ? -1 : get_rgb(state, cell.bg); - int vt_fg_idx = ((!fg_default && VTERM_COLOR_IS_INDEXED(&cell.fg)) - ? cell.fg.indexed.idx + 1 : 0); - int vt_bg_idx = ((!bg_default && VTERM_COLOR_IS_INDEXED(&cell.bg)) - ? cell.bg.indexed.idx + 1 : 0); + bool fg_indexed = VTERM_COLOR_IS_INDEXED(&cell.fg); + bool bg_indexed = VTERM_COLOR_IS_INDEXED(&cell.bg); + + int vt_fg_idx = ((!fg_default && fg_indexed) ? cell.fg.indexed.idx + 1 : 0); + int vt_bg_idx = ((!bg_default && bg_indexed) ? cell.bg.indexed.idx + 1 : 0); + + bool fg_set = vt_fg_idx && vt_fg_idx <= 16 && term->color_set[vt_fg_idx-1]; + bool bg_set = vt_bg_idx && vt_bg_idx <= 16 && term->color_set[vt_bg_idx-1]; int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0) | (cell.attrs.italic ? HL_ITALIC : 0) | (cell.attrs.reverse ? HL_INVERSE : 0) | (cell.attrs.underline ? HL_UNDERLINE : 0) - | (cell.attrs.strike ? HL_STRIKETHROUGH: 0); + | (cell.attrs.strike ? HL_STRIKETHROUGH: 0) + | ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0) + | ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0); int attr_id = 0; diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 84f636077d..a5d83d6a25 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -252,6 +252,8 @@ func GetVimProg() endif endfunc +let g:valgrind_cnt = 1 + " Get the command to run Vim, with -u NONE and --headless arguments. " If there is an argument use it instead of "NONE". func GetVimCommand(...) @@ -267,6 +269,13 @@ func GetVimCommand(...) endif let cmd .= ' --headless -i NONE' let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') + + " If using valgrind, make sure every run uses a different log file. + if cmd =~ 'valgrind.*--log-file=' + let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let g:valgrind_cnt += 1 + endif + return cmd endfunc @@ -290,9 +299,6 @@ endfunc func RunVimPiped(before, after, arguments, pipecmd) let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' let cmd = GetVimCommand() - if cmd == '' - return 0 - endif let args = '' if len(a:before) > 0 call writefile(a:before, 'Xbefore.vim') diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index f1274b01c8..5668f45dea 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,6 +2,7 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_backup.vim source test_behave.vim source test_cd.vim source test_changedtick.vim diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim new file mode 100644 index 0000000000..fa10430613 --- /dev/null +++ b/src/nvim/testdir/test_backup.vim @@ -0,0 +1,58 @@ +" Tests for the backup function + +func Test_backup() + set backup backupdir=. + new + call setline(1, ['line1', 'line2']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + let l = readfile('Xbackup.txt~') + call assert_equal(['line1', 'line2'], l) + bw! + set backup&vim backupdir&vim + call delete('Xbackup.txt') + call delete('Xbackup.txt~') +endfunc + +func Test_backup2() + set backup backupdir=.// + new + call setline(1, ['line1', 'line2', 'line3']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + sp *Xbackup.txt~ + call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) + let f=expand('%') + call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + bw! + bw! + call delete('Xbackup.txt') + call delete(f) + set backup&vim backupdir&vim +endfunc + +func Test_backup2_backupcopy() + set backup backupdir=.// backupcopy=yes + new + call setline(1, ['line1', 'line2', 'line3']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + sp *Xbackup.txt~ + call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) + let f=expand('%') + call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + bw! + bw! + call delete('Xbackup.txt') + call delete(f) + set backup&vim backupdir&vim backupcopy&vim +endfunc diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 57b19aa817..21e0271bda 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -773,3 +773,28 @@ func Test_diff_of_diff() call StopVimInTerminal(buf) call delete('Xtest_diff_diff') endfunc + +func CloseoffSetup() + enew + call setline(1, ['one', 'two', 'three']) + diffthis + new + call setline(1, ['one', 'tow', 'three']) + diffthis + call assert_equal(1, &diff) + only! +endfunc + +func Test_diff_closeoff() + " "closeoff" included by default: last diff win gets 'diff' reset' + call CloseoffSetup() + call assert_equal(0, &diff) + enew! + + " "closeoff" excluded: last diff win keeps 'diff' set' + set diffopt-=closeoff + call CloseoffSetup() + call assert_equal(1, &diff) + diffoff! + enew! +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index a36c51f56f..7822507f86 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1132,6 +1132,13 @@ func Test_reg_executing_and_recording() " :normal command saves and restores reg_executing let s:reg_stat = '' + let @q = ":call TestFunc()\<CR>:call s:save_reg_stat()\<CR>" + func TestFunc() abort + normal! ia + endfunc + call feedkeys("@q", 'xt') + call assert_equal(':q', s:reg_stat) + delfunc TestFunc " getchar() command saves and restores reg_executing map W :call TestFunc()<CR> diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index accd21e9a3..d301874891 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -99,3 +99,28 @@ func Test_gf() call delete('Xtest1') call delete('Xtestgf') endfunc + +func Test_gf_visual() + call writefile([], "Xtest_gf_visual") + new + call setline(1, 'XXXtest_gf_visualXXX') + set hidden + + " Visually select Xtest_gf_visual and use gf to go to that file + norm! ttvtXgf + call assert_equal('Xtest_gf_visual', bufname('%')) + + bwipe! + call delete('Xtest_gf_visual') + set hidden& +endfunc + +func Test_gf_error() + new + call assert_fails('normal gf', 'E446:') + call assert_fails('normal gF', 'E446:') + call setline(1, '/doesnotexist') + call assert_fails('normal gf', 'E447:') + call assert_fails('normal gF', 'E447:') + bwipe! +endfunc diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index 5e74289b00..d41675be0c 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -129,6 +129,33 @@ func Test_gn_command() call assert_equal([' nnoremap', '', 'match'], getline(1,'$')) sil! %d_ + " make sure it works correctly for one-char wide search items + call setline('.', ['abcdefghi']) + let @/ = 'a' + exe "norm! 0fhvhhgNgU" + call assert_equal(['ABCDEFGHi'], getline(1,'$')) + call setline('.', ['abcdefghi']) + let @/ = 'b' + " this gn wraps around the end of the file + exe "norm! 0fhvhhgngU" + call assert_equal(['aBCDEFGHi'], getline(1,'$')) + sil! %d _ + call setline('.', ['abcdefghi']) + let @/ = 'f' + exe "norm! 0vllgngU" + call assert_equal(['ABCDEFghi'], getline(1,'$')) + sil! %d _ + call setline('.', ['12345678']) + let @/ = '5' + norm! gg0f7vhhhhgnd + call assert_equal(['12348'], getline(1,'$')) + sil! %d _ + call setline('.', ['12345678']) + let @/ = '5' + norm! gg0f2vf7gNd + call assert_equal(['1678'], getline(1,'$')) + sil! %d _ + set wrapscan&vim set belloff&vim endfu diff --git a/src/nvim/testdir/test_join.vim b/src/nvim/testdir/test_join.vim index 1c97414164..ecb55c9af6 100644 --- a/src/nvim/testdir/test_join.vim +++ b/src/nvim/testdir/test_join.vim @@ -9,6 +9,27 @@ func Test_join_with_count() call setline(1, ['one', 'two', 'three', 'four']) normal 10J call assert_equal('one two three four', getline(1)) + + call setline(1, ['one', '', 'two']) + normal J + call assert_equal('one', getline(1)) + + call setline(1, ['one', ' ', 'two']) + normal J + call assert_equal('one', getline(1)) + + call setline(1, ['one', '', '', 'two']) + normal JJ + call assert_equal('one', getline(1)) + + call setline(1, ['one', ' ', ' ', 'two']) + normal JJ + call assert_equal('one', getline(1)) + + call setline(1, ['one', '', '', 'two']) + normal 2J + call assert_equal('one', getline(1)) + quit! endfunc diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 1fce3d6937..3c0fefbd25 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -141,6 +141,11 @@ func Test_let_varg_fail() call s:set_varg8([0]) endfunction +func Test_let_utf8_environment() + let $a = 'ĀĒĪŌŪあいうえお' + call assert_equal('ĀĒĪŌŪあいうえお', $a) +endfunc + func Test_let_heredoc_fails() call assert_fails('let v =<< marker', 'E991:') @@ -284,4 +289,12 @@ E END endif call assert_equal([], check) + + " unpack assignment + let [a, b, c] =<< END + x + \y + z +END + call assert_equal([' x', ' \y', ' z'], [a, b, c]) endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 07d250cace..eab638d19a 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1895,6 +1895,7 @@ fun! Test_normal33_g_cmd2() set wrap listchars= sbr= let lineA='abcdefghijklmnopqrstuvwxyz' let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $put =lineA $put =lineB @@ -1928,9 +1929,30 @@ fun! Test_normal33_g_cmd2() call assert_equal(15, col('.')) call assert_equal('l', getreg(0)) + norm! 2ggdd + $put =lineC + + " Test for gM + norm! gMyl + call assert_equal(73, col('.')) + call assert_equal('0', getreg(0)) + " Test for 20gM + norm! 20gMyl + call assert_equal(29, col('.')) + call assert_equal('S', getreg(0)) + " Test for 60gM + norm! 60gMyl + call assert_equal(87, col('.')) + call assert_equal('E', getreg(0)) + + " Test for g Ctrl-G + set ff=unix + let a=execute(":norm! g\<c-g>") + call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a) + " Test for gI norm! gIfoo - call assert_equal(['', 'fooabcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) + call assert_equal(['', 'foo0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'], getline(1,'$')) " Test for gi wincmd c diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index fc514fc9e6..15cbf52cb5 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -37,6 +37,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgrepadd <mods> grepadd <args> command! -nargs=* Xhelpgrep helpgrep <args> command! -nargs=0 -count Xcc <count>cc + command! -count=1 -nargs=0 Xbelow <mods><count>cbelow + command! -count=1 -nargs=0 Xabove <mods><count>cabove let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') call setqflist([], 'f') @@ -70,6 +72,8 @@ func s:setup_commands(cchar) command! -nargs=* Xgrepadd <mods> lgrepadd <args> command! -nargs=* Xhelpgrep lhelpgrep <args> command! -nargs=0 -count Xcc <count>ll + command! -count=1 -nargs=0 Xbelow <mods><count>lbelow + command! -count=1 -nargs=0 Xabove <mods><count>labove let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) call setloclist(0, [], 'f') @@ -163,6 +167,12 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + " No location list for the current window + call assert_fails('lolder', 'E776:') + call assert_fails('lnewer', 'E776:') + endif + let list = [{'bufnr': bufnr('%'), 'lnum': 1}] call g:Xsetlist(list) @@ -273,6 +283,27 @@ func Test_cwindow() call XwindowTests('l') endfunc +func Test_copenHeight() + copen + wincmd H + let height = winheight(0) + copen 10 + call assert_equal(height, winheight(0)) + quit +endfunc + +func Test_copenHeight_tabline() + set tabline=foo showtabline=2 + copen + wincmd H + let height = winheight(0) + copen 10 + call assert_equal(height, winheight(0)) + quit + set tabline& showtabline& +endfunc + + " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. func XfileTests(cchar) @@ -540,6 +571,8 @@ func s:test_xhelpgrep(cchar) " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') + " Invalid regular expression + call assert_fails('Xhelpgrep \@<!', 'E480:') endfunc func Test_helpgrep() @@ -1045,8 +1078,8 @@ func Test_efm2() set efm=%f:%s cexpr 'Xtestfile:Line search text' let l = getqflist() - call assert_equal(l[0].pattern, '^\VLine search text\$') - call assert_equal(l[0].lnum, 0) + call assert_equal('^\VLine search text\$', l[0].pattern) + call assert_equal(0, l[0].lnum) let l = split(execute('clist', ''), "\n") call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) @@ -1287,6 +1320,28 @@ func SetXlistTests(cchar, bnum) let l = g:Xgetlist() call g:Xsetlist(l) call assert_equal(0, g:Xgetlist()[0].valid) + " Adding a non-valid entry should not mark the list as having valid entries + call g:Xsetlist([{'bufnr':a:bnum, 'lnum':5, 'valid':0}], 'a') + Xwindow + call assert_equal(1, winnr('$')) + + " :cnext/:cprev should still work even with invalid entries in the list + let l = [{'bufnr' : a:bnum, 'lnum' : 1, 'text' : '1', 'valid' : 0}, + \ {'bufnr' : a:bnum, 'lnum' : 2, 'text' : '2', 'valid' : 0}] + call g:Xsetlist(l) + Xnext + call assert_equal(2, g:Xgetlist({'idx' : 0}).idx) + Xprev + call assert_equal(1, g:Xgetlist({'idx' : 0}).idx) + " :cnext/:cprev should still work after appending invalid entries to an + " empty list + call g:Xsetlist([]) + call g:Xsetlist(l, 'a') + Xnext + call assert_equal(2, g:Xgetlist({'idx' : 0}).idx) + Xprev + call assert_equal(1, g:Xgetlist({'idx' : 0}).idx) + call g:Xsetlist([{'text':'Text1', 'valid':1}]) Xwindow call assert_equal(2, winnr('$')) @@ -1963,6 +2018,18 @@ func Xproperty_tests(cchar) call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title) + " Test for getting id of window associated with a location list window + if a:cchar == 'l' + only + call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid) + let wid = win_getid() + Xopen + call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid) + wincmd w + call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid) + only + endif + " The following used to crash Vim with address sanitizer call g:Xsetlist([], 'f') call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]}) @@ -2503,7 +2570,7 @@ func Test_file_from_copen() cclose augroup! QF_Test -endfunction +endfunc func Test_resize_from_copen() augroup QF_Test @@ -2522,6 +2589,94 @@ func Test_resize_from_copen() endtry endfunc +" Test for aborting quickfix commands using QuickFixCmdPre +func Xtest_qfcmd_abort(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + + " cexpr/lexpr + let e = '' + try + Xexpr ["F1:10:Line10", "F2:20:Line20"] + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " cfile/lfile + call writefile(["F1:10:Line10", "F2:20:Line20"], 'Xfile1') + let e = '' + try + Xfile Xfile1 + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + call delete('Xfile1') + + " cgetbuffer/lgetbuffer + enew! + call append(0, ["F1:10:Line10", "F2:20:Line20"]) + let e = '' + try + Xgetbuffer + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + enew! + + " vimgrep/lvimgrep + let e = '' + try + Xvimgrep /func/ test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " helpgrep/lhelpgrep + let e = '' + try + Xhelpgrep quickfix + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + + " grep/lgrep + if has('unix') + let e = '' + try + silent Xgrep func test_quickfix.vim + catch /.*/ + let e = v:exception + endtry + call assert_equal('AbortCmd', e) + call assert_equal(0, g:Xgetlist({'nr' : '$'}).nr) + endif +endfunc + +func Test_qfcmd_abort() + augroup QF_Test + au! + autocmd QuickFixCmdPre * throw "AbortCmd" + augroup END + + call Xtest_qfcmd_abort('c') + call Xtest_qfcmd_abort('l') + + augroup QF_Test + au! + augroup END +endfunc + " Tests for the quickfix buffer b:changedtick variable func Xchangedtick_tests(cchar) call s:setup_commands(a:cchar) @@ -3075,7 +3230,17 @@ func Xgetlist_empty_tests(cchar) 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})) + if a:cchar == 'c' + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, + \ 'items' : [], 'nr' : 0, 'size' : 0, + \ 'title' : '', 'winid' : 0, 'changedtick': 0}, + \ g:Xgetlist({'all' : 0})) + else + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, + \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', + \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0}, + \ g:Xgetlist({'all' : 0})) + endif " Quickfix window with empty stack silent! Xopen @@ -3108,7 +3273,16 @@ func Xgetlist_empty_tests(cchar) 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})) + if a:cchar == 'c' + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) + else + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0}, + \ g:Xgetlist({'id' : qfid, 'all' : 0})) + endif " Non-existing quickfix list number call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context) @@ -3120,7 +3294,16 @@ func Xgetlist_empty_tests(cchar) 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})) + if a:cchar == 'c' + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0})) + else + call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], + \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0}, + \ g:Xgetlist({'nr' : 5, 'all' : 0})) + endif endfunc func Test_getqflist() @@ -3209,7 +3392,28 @@ func Test_lexpr_crash() augroup QF_Test au! augroup END + enew | only + augroup QF_Test + au! + au BufNew * call setloclist(0, [], 'f') + augroup END + lexpr 'x:1:x' + augroup QF_Test + au! + augroup END + + enew | only + lexpr '' + lopen + augroup QF_Test + au! + au FileType * call setloclist(0, [], 'f') + augroup END + lexpr '' + augroup QF_Test + au! + augroup END endfunc " The following test used to crash Vim @@ -3603,3 +3807,167 @@ func Test_curswant() call assert_equal(getcurpos()[4], virtcol('.')) cclose | helpclose endfunc + +" Test for parsing entries using visual screen column +func Test_viscol() + enew + call writefile(["Col1\tCol2\tCol3"], 'Xfile1') + edit Xfile1 + + " Use byte offset for column number + set efm& + cexpr "Xfile1:1:5:XX\nXfile1:1:9:YY\nXfile1:1:20:ZZ" + call assert_equal([5, 8], [col('.'), virtcol('.')]) + cnext + call assert_equal([9, 12], [col('.'), virtcol('.')]) + cnext + call assert_equal([14, 20], [col('.'), virtcol('.')]) + + " Use screen column offset for column number + set efm=%f:%l:%v:%m + cexpr "Xfile1:1:8:XX\nXfile1:1:12:YY\nXfile1:1:20:ZZ" + call assert_equal([5, 8], [col('.'), virtcol('.')]) + cnext + call assert_equal([9, 12], [col('.'), virtcol('.')]) + cnext + call assert_equal([14, 20], [col('.'), virtcol('.')]) + cexpr "Xfile1:1:6:XX\nXfile1:1:15:YY\nXfile1:1:24:ZZ" + call assert_equal([5, 8], [col('.'), virtcol('.')]) + cnext + call assert_equal([10, 16], [col('.'), virtcol('.')]) + cnext + call assert_equal([14, 20], [col('.'), virtcol('.')]) + + enew + call writefile(["Col1\täü\töß\tCol4"], 'Xfile1') + + " Use byte offset for column number + set efm& + cexpr "Xfile1:1:8:XX\nXfile1:1:11:YY\nXfile1:1:16:ZZ" + call assert_equal([8, 10], [col('.'), virtcol('.')]) + cnext + call assert_equal([11, 17], [col('.'), virtcol('.')]) + cnext + call assert_equal([16, 25], [col('.'), virtcol('.')]) + + " Use screen column offset for column number + set efm=%f:%l:%v:%m + cexpr "Xfile1:1:10:XX\nXfile1:1:17:YY\nXfile1:1:25:ZZ" + call assert_equal([8, 10], [col('.'), virtcol('.')]) + cnext + call assert_equal([11, 17], [col('.'), virtcol('.')]) + cnext + call assert_equal([16, 25], [col('.'), virtcol('.')]) + + enew | only + set efm& + call delete('Xfile1') +endfunc + +" Test for the :cbelow, :cabove, :lbelow and :labove commands. +func Xtest_below(cchar) + call s:setup_commands(a:cchar) + + " No quickfix/location list + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + + " Empty quickfix/location list + call g:Xsetlist([]) + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + + call s:create_test_file('X1') + call s:create_test_file('X2') + call s:create_test_file('X3') + call s:create_test_file('X4') + + " Invalid entries + edit X1 + call g:Xsetlist(["E1", "E2"]) + call assert_fails('Xbelow', 'E42:') + call assert_fails('Xabove', 'E42:') + call assert_fails('3Xbelow', 'E42:') + call assert_fails('4Xabove', 'E42:') + + " Test the commands with various arguments + Xexpr ["X1:5:L5", "X2:5:L5", "X2:10:L10", "X2:15:L15", "X3:3:L3"] + edit +7 X2 + Xabove + call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_fails('Xabove', 'E553:') + normal 2j + Xbelow + call assert_equal(['X2', 10], [bufname(''), line('.')]) + " Last error in this file + Xbelow 99 + call assert_equal(['X2', 15], [bufname(''), line('.')]) + call assert_fails('Xbelow', 'E553:') + " First error in this file + Xabove 99 + call assert_equal(['X2', 5], [bufname(''), line('.')]) + call assert_fails('Xabove', 'E553:') + normal gg + Xbelow 2 + call assert_equal(['X2', 10], [bufname(''), line('.')]) + normal G + Xabove 2 + call assert_equal(['X2', 10], [bufname(''), line('.')]) + edit X4 + call assert_fails('Xabove', 'E42:') + call assert_fails('Xbelow', 'E42:') + if a:cchar == 'l' + " If a buffer has location list entries from some other window but not + " from the current window, then the commands should fail. + edit X1 | split | call setloclist(0, [], 'f') + call assert_fails('Xabove', 'E776:') + call assert_fails('Xbelow', 'E776:') + close + endif + + " Test for lines with multiple quickfix entries + Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3", + \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3", + \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"] + edit +1 X2 + Xbelow 2 + call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + normal gg + Xbelow 99 + call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + normal G + Xabove 2 + call assert_equal(['X2', 10, 1], [bufname(''), line('.'), col('.')]) + normal G + Xabove 99 + call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + normal 10G + Xabove + call assert_equal(['X2', 5, 1], [bufname(''), line('.'), col('.')]) + normal 10G + Xbelow + call assert_equal(['X2', 15, 1], [bufname(''), line('.'), col('.')]) + + " Invalid range + if a:cchar == 'c' + call assert_fails('-2cbelow', 'E553:') + " TODO: should go to first error in the current line? + 0cabove + else + call assert_fails('-2lbelow', 'E553:') + " TODO: should go to first error in the current line? + 0labove + endif + + call delete('X1') + call delete('X2') + call delete('X3') + call delete('X4') +endfunc + +func Test_cbelow() + call Xtest_below('c') + call Xtest_below('l') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index e49b5542fa..e2016d7927 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -283,9 +283,9 @@ func Test_zz_affix() \ ]) call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) - call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + call RunGoodBad("meea1 meezero meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", - \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"], + \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "meezero", "prebar", "prebarmeat", "tail"], \ [ \ ["bad", ["bar", "lead", "tail"]], \ ["mee", ["meea1", "meea\xE9", "bar"]], @@ -320,6 +320,19 @@ func Test_zz_Numbers() \ ]) endfunc +" Affix flags +func Test_zz_affix_flags() + call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10) + call RunGoodBad("drink drinkable drinkables drinktable drinkabletable", + \ "bad: drinks drinkstable drinkablestable", + \ ["drink", "drinkable", "drinkables", "table"], + \ [['bad', []], + \ ['drinks', ['drink']], + \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']], + \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']], + \ ]) +endfunc + function FirstSpellWord() call feedkeys("/^start:\n", 'tx') normal ]smm @@ -713,6 +726,9 @@ let g:test_data_aff7 = [ \"SFX 61003 Y 1", \"SFX 61003 0 meat .", \"", + \"SFX 0 Y 1", + \"SFX 0 0 zero .", + \"", \"SFX 391 Y 1", \"SFX 391 0 a1 .", \"", @@ -724,7 +740,7 @@ let g:test_data_aff7 = [ \ ] let g:test_data_dic7 = [ \"1234", - \"mee/391,111,9999", + \"mee/0,391,111,9999", \"bar/17,61003,123", \"lead/2", \"tail/123", @@ -748,6 +764,21 @@ let g:test_data_dic9 = [ \"foo", \"bar", \ ] +let g:test_data_aff10 = [ + \"COMPOUNDRULE se", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX A Y 1", + \"SFX A 0 able/Mp .", + \"", + \"SFX M Y 1", + \"SFX M 0 s .", + \ ] +let g:test_data_dic10 = [ + \"1234", + \"drink/As", + \"table/e", + \ ] let g:test_data_aff_sal = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index b29b678129..e209310a05 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -149,6 +149,7 @@ func Run_SubCmd_Tests(tests) for t in a:tests let start = line('.') + 1 let end = start + len(t[2]) - 1 + " TODO: why is there a one second delay the first time we get here? exe "normal o" . t[0] call cursor(start, 1) exe t[1] @@ -717,3 +718,12 @@ one two close! endfunc + +func Test_sub_beyond_end() + new + call setline(1, '#') + let @/ = '^#\n\zs' + s///e + call assert_equal('#', getline(1)) + bwipe! +endfunc diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index ce527a5e1d..f93af76f17 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -466,4 +466,28 @@ func Test_tag_line_toolong() let &verbose = old_vbs endfunc +func Test_tagline() + call writefile([ + \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo', + \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar', + \], 'Xtags') + call writefile([ + \ ' def provision(self, **kwargs):', + \ ' pass', + \ ' def provision(self, **kwargs):', + \ ' pass', + \], 'Xtest.py') + + set tags=Xtags + + 1tag provision + call assert_equal(line('.'), 1) + 2tag provision + call assert_equal(line('.'), 3) + + call delete('Xtags') + call delete('Xtest.py') + set tags& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 9194e0014d..448b2dc51c 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -48,6 +48,9 @@ func Test_quote_selection_selection_exclusive() set selection=exclusive exe "norm! fdvhi'y" call assert_equal('bcde', @") + let @"='dummy' + exe "norm! $gevi'y" + call assert_equal('bcde', @") set selection&vim bw! endfunc diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 3fcba4134e..d2f13ff072 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1284,7 +1284,7 @@ func s:DoNothing() endfunc func Test_script_local_func() - set nocp viminfo+=nviminfo + set nocp nomore viminfo+=nviminfo new nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 67adede8d7..1e6b26a057 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -73,3 +73,12 @@ func Test_edit_CTRL_G() bwipe! set virtualedit= endfunc + +func Test_edit_change() + new + set virtualedit=all + call setline(1, "\t⒌") + normal Cx + call assert_equal('x', getline(1)) + bwipe! +endfunc diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 844bc0db40..c71378463f 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -26,7 +26,7 @@ void tinput_init(TermInput *input, Loop *loop) { input->loop = loop; input->paste = 0; - input->in_fd = 0; + input->in_fd = STDIN_FILENO; input->waiting_for_bg_response = 0; input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE); uv_mutex_init(&input->key_buffer_mutex); @@ -36,7 +36,7 @@ void tinput_init(TermInput *input, Loop *loop) // echo q | nvim -es // ls *.md | xargs nvim #ifdef WIN32 - if (!os_isatty(0)) { + if (!os_isatty(input->in_fd)) { const HANDLE conin_handle = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, @@ -46,8 +46,8 @@ void tinput_init(TermInput *input, Loop *loop) assert(input->in_fd != -1); } #else - if (!os_isatty(0) && os_isatty(2)) { - input->in_fd = 2; + if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) { + input->in_fd = STDERR_FILENO; } #endif input_global_fd_init(input->in_fd); diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 945b093f32..60e1353000 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -220,7 +220,7 @@ static void terminfo_start(UI *ui) data->unibi_ext.reset_cursor_style = -1; data->unibi_ext.get_bg = -1; data->unibi_ext.set_underline_color = -1; - data->out_fd = 1; + data->out_fd = STDOUT_FILENO; data->out_isatty = os_isatty(data->out_fd); const char *term = os_getenv("TERM"); @@ -515,20 +515,8 @@ static void update_attrs(UI *ui, int attr_id) } data->print_attr_id = attr_id; HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id); - - int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1); - if (fg == -1) { - fg = ui->rgb ? data->clear_attrs.rgb_fg_color - : (data->clear_attrs.cterm_fg_color - 1); - } - - int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1); - if (bg == -1) { - bg = ui->rgb ? data->clear_attrs.rgb_bg_color - : (data->clear_attrs.cterm_bg_color - 1); - } - int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; + bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; bool reverse = attr & HL_INVERSE; @@ -596,14 +584,29 @@ static void update_attrs(UI *ui, int attr_id) unibi_out_ext(ui, data->unibi_ext.set_underline_color); } } - if (ui->rgb) { + + int fg, bg; + if (ui->rgb && !(attr & HL_FG_INDEXED)) { + fg = ((attrs.rgb_fg_color != -1) + ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color); if (fg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground); } + } else { + fg = (attrs.cterm_fg_color + ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1)); + if (fg != -1) { + UNIBI_SET_NUM_VAR(data->params[0], fg); + unibi_out(ui, unibi_set_a_foreground); + } + } + if (ui->rgb && !(attr & HL_BG_INDEXED)) { + bg = ((attrs.rgb_bg_color != -1) + ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color); if (bg != -1) { UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green @@ -611,17 +614,15 @@ static void update_attrs(UI *ui, int attr_id) unibi_out_ext(ui, data->unibi_ext.set_rgb_background); } } else { - if (fg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], fg); - unibi_out(ui, unibi_set_a_foreground); - } - + bg = (attrs.cterm_bg_color + ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1)); if (bg != -1) { UNIBI_SET_NUM_VAR(data->params[0], bg); unibi_out(ui, unibi_set_a_background); } } + data->default_attr = fg == -1 && bg == -1 && !bold && !italic && !underline && !undercurl && !reverse && !standout && !strikethrough; diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 035613c7fd..539d42765d 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -91,7 +91,9 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/buffer_updates.h" +#include "nvim/pos.h" // MAXLNUM #include "nvim/mark.h" +#include "nvim/mark_extended.h" #include "nvim/memline.h" #include "nvim/message.h" #include "nvim/misc1.h" @@ -106,6 +108,7 @@ #include "nvim/types.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +#include "nvim/lib/kvec.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "undo.c.generated.h" @@ -222,9 +225,6 @@ int u_save_cursor(void) */ int u_save(linenr_T top, linenr_T bot) { - if (undo_off) - return OK; - if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) { return FAIL; /* rely on caller to do error messages */ } @@ -243,10 +243,7 @@ int u_save(linenr_T top, linenr_T bot) */ int u_savesub(linenr_T lnum) { - if (undo_off) - return OK; - - return u_savecommon(lnum - 1, lnum + 1, lnum + 1, FALSE); + return u_savecommon(lnum - 1, lnum + 1, lnum + 1, false); } /* @@ -257,10 +254,7 @@ int u_savesub(linenr_T lnum) */ int u_inssub(linenr_T lnum) { - if (undo_off) - return OK; - - return u_savecommon(lnum - 1, lnum, lnum + 1, FALSE); + return u_savecommon(lnum - 1, lnum, lnum + 1, false); } /* @@ -272,9 +266,6 @@ int u_inssub(linenr_T lnum) */ int u_savedel(linenr_T lnum, long nlines) { - if (undo_off) - return OK; - return u_savecommon(lnum - 1, lnum + nlines, nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, FALSE); } @@ -384,6 +375,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) * up the undo info when out of memory. */ uhp = xmalloc(sizeof(u_header_T)); + kv_init(uhp->uh_extmark); #ifdef U_DEBUG uhp->uh_magic = UH_MAGIC; #endif @@ -2249,10 +2241,10 @@ static void u_undoredo(int undo, bool do_buf_event) xfree((char_u *)uep->ue_array); } - /* adjust marks */ + // Adjust marks if (oldsize != newsize) { mark_adjust(top + 1, top + oldsize, (long)MAXLNUM, - (long)newsize - (long)oldsize, false); + (long)newsize - (long)oldsize, false, kExtmarkNOOP); if (curbuf->b_op_start.lnum > top + oldsize) { curbuf->b_op_start.lnum += newsize - oldsize; } @@ -2285,6 +2277,23 @@ static void u_undoredo(int undo, bool do_buf_event) newlist = uep; } + // Adjust Extmarks + ExtmarkUndoObject undo_info; + if (undo) { + for (i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) { + undo_info = kv_A(curhead->uh_extmark, i); + extmark_apply_undo(undo_info, undo); + } + // redo + } else { + for (i = 0; i < (int)kv_size(curhead->uh_extmark); i++) { + undo_info = kv_A(curhead->uh_extmark, i); + extmark_apply_undo(undo_info, undo); + } + } + // finish Adjusting extmarks + + curhead->uh_entry = newlist; curhead->uh_flags = new_flags; if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) { @@ -2828,6 +2837,8 @@ u_freeentries( u_freeentry(uep, uep->ue_size); } + kv_destroy(uhp->uh_extmark); + #ifdef U_DEBUG uhp->uh_magic = 0; #endif @@ -2902,9 +2913,6 @@ void u_undoline(void) colnr_T t; char_u *oldp; - if (undo_off) - return; - if (curbuf->b_u_line_ptr == NULL || curbuf->b_u_line_lnum > curbuf->b_ml.ml_line_count) { beep_flush(); @@ -3022,3 +3030,34 @@ list_T *u_eval_tree(const u_header_T *const first_uhp) return list; } + +// Given the buffer, Return the undo header. If none is set, set one first. +// NULL will be returned if e.g undolevels = -1 (undo disabled) +u_header_T *u_force_get_undo_header(buf_T *buf) +{ + u_header_T *uhp = NULL; + if (buf->b_u_curhead != NULL) { + uhp = buf->b_u_curhead; + } else if (buf->b_u_newhead) { + uhp = buf->b_u_newhead; + } + // Create the first undo header for the buffer + if (!uhp) { + // Undo is normally invoked in change code, which already has swapped + // curbuf. + buf_T *save_curbuf = curbuf; + curbuf = buf; + // Args are tricky: this means replace empty range by empty range.. + u_savecommon(0, 1, 1, true); + curbuf = save_curbuf; + + uhp = buf->b_u_curhead; + if (!uhp) { + uhp = buf->b_u_newhead; + if (get_undolevel() > 0 && !uhp) { + abort(); + } + } + } + return uhp; +} diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 6c7e2bba41..0fa3b415ec 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -4,6 +4,7 @@ #include <time.h> // for time_t #include "nvim/pos.h" +#include "nvim/mark_extended_defs.h" #include "nvim/mark_defs.h" typedef struct u_header u_header_T; @@ -56,14 +57,15 @@ struct u_header { u_entry_T *uh_getbot_entry; /* pointer to where ue_bot must be set */ pos_T uh_cursor; /* cursor position before saving */ long uh_cursor_vcol; - int uh_flags; /* see below */ - fmark_T uh_namedm[NMARKS]; /* marks before undo/after redo */ - visualinfo_T uh_visual; /* Visual areas before undo/after redo */ - time_t uh_time; /* timestamp when the change was made */ - long uh_save_nr; /* set when the file was saved after the - changes in this block */ + int uh_flags; // see below + fmark_T uh_namedm[NMARKS]; // marks before undo/after redo + extmark_undo_vec_t uh_extmark; // info to move extmarks + visualinfo_T uh_visual; // Visual areas before undo/after redo + time_t uh_time; // timestamp when the change was made + long uh_save_nr; // set when the file was saved after the + // changes in this block #ifdef U_DEBUG - int uh_magic; /* magic number to check allocation */ + int uh_magic; // magic number to check allocation #endif }; diff --git a/src/nvim/version.c b/src/nvim/version.c index b6122f6463..f678b743c2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -83,7 +83,7 @@ static const int included_patches[] = { 1838, 1837, 1836, - // 1835, + 1835, 1834, 1833, 1832, @@ -120,7 +120,7 @@ static const int included_patches[] = { 1801, 1800, 1799, - // 1798, + 1798, 1797, 1796, 1795, @@ -176,7 +176,7 @@ static const int included_patches[] = { // 1745, // 1744, // 1743, - // 1742, + 1742, 1741, 1740, 1739, @@ -193,10 +193,10 @@ static const int included_patches[] = { 1728, 1727, 1726, - // 1725, + 1725, 1724, 1723, - // 1722, + 1722, 1721, 1720, 1719, @@ -207,7 +207,7 @@ static const int included_patches[] = { 1714, 1713, // 1712, - // 1711, + 1711, 1710, 1709, 1708, @@ -233,7 +233,7 @@ static const int included_patches[] = { 1688, 1687, 1686, - // 1685, + 1685, 1684, 1683, 1682, @@ -252,8 +252,8 @@ static const int included_patches[] = { 1669, // 1668, 1667, - // 1666, - // 1665, + 1666, + 1665, 1664, 1663, 1662, @@ -294,12 +294,12 @@ static const int included_patches[] = { 1627, 1626, 1625, - // 1624, + 1624, 1623, 1622, 1621, 1620, - // 1619, + 1619, 1618, // 1617, // 1616, @@ -336,7 +336,7 @@ static const int included_patches[] = { 1585, 1584, 1583, - // 1582, + 1582, 1581, 1580, 1579, @@ -426,7 +426,7 @@ static const int included_patches[] = { // 1495, 1494, 1493, - // 1492, + 1492, // 1491, 1490, 1489, @@ -467,7 +467,7 @@ static const int included_patches[] = { // 1454, 1453, 1452, - // 1451, + 1451, 1450, // 1449, 1448, @@ -742,7 +742,7 @@ static const int included_patches[] = { 1179, 1178, 1177, - // 1176, + 1176, 1175, 1174, 1173, @@ -984,7 +984,7 @@ static const int included_patches[] = { 937, 936, 935, - // 934, + 934, 933, 932, 931, diff --git a/src/nvim/window.c b/src/nvim/window.c index ce5be8e904..2a7578e33c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1530,8 +1530,9 @@ static void win_init(win_T *newp, win_T *oldp, int flags) /* Don't copy the location list. */ newp->w_llist = NULL; newp->w_llist_ref = NULL; - } else - copy_loclist(oldp, newp); + } else { + copy_loclist_stack(oldp, newp); + } newp->w_localdir = (oldp->w_localdir == NULL) ? NULL : vim_strsave(oldp->w_localdir); @@ -1574,7 +1575,7 @@ static void win_init_some(win_T *newp, win_T *oldp) /// Check if "win" is a pointer to an existing window in the current tabpage. /// /// @param win window to check -bool win_valid(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (win == NULL) { return false; @@ -2417,6 +2418,7 @@ int win_close(win_T *win, bool free_buf) bool help_window = false; tabpage_T *prev_curtab = curtab; frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; + const bool had_diffmode = win->w_p_diff; if (last_window() && !win->w_floating) { EMSG(_("E444: Cannot close last window")); @@ -2641,6 +2643,22 @@ int win_close(win_T *win, bool free_buf) if (help_window) restore_snapshot(SNAP_HELP_IDX, close_curwin); + // If the window had 'diff' set and now there is only one window left in + // the tab page with 'diff' set, and "closeoff" is in 'diffopt', then + // execute ":diffoff!". + if (diffopt_closeoff() && had_diffmode && curtab == prev_curtab) { + int diffcount = 0; + + FOR_ALL_WINDOWS_IN_TAB(dwin, curtab) { + if (dwin->w_p_diff) { + diffcount++; + } + } + if (diffcount == 1) { + do_cmdline_cmd("diffoff!"); + } + } + curwin->w_pos_changed = true; redraw_all_later(NOT_VALID); return OK; |