diff options
-rw-r--r-- | runtime/doc/api.txt | 24 | ||||
-rw-r--r-- | runtime/doc/change.txt | 8 | ||||
-rw-r--r-- | runtime/doc/index.txt | 2 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 166 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 112 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 67 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 31 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 7 | ||||
-rw-r--r-- | src/nvim/api/window.c | 32 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 4 | ||||
-rw-r--r-- | src/nvim/normal.c | 12 | ||||
-rw-r--r-- | src/nvim/ops.c | 33 | ||||
-rw-r--r-- | src/nvim/ops.h | 15 | ||||
-rw-r--r-- | src/nvim/regexp.c | 6 | ||||
-rw-r--r-- | src/nvim/testdir/test_blockedit.vim | 1 | ||||
-rw-r--r-- | src/nvim/testdir/test_normal.vim | 38 | ||||
-rw-r--r-- | src/nvim/testdir/test_search.vim | 20 | ||||
-rw-r--r-- | src/nvim/testdir/test_visual.vim | 270 | ||||
-rw-r--r-- | src/nvim/window.c | 39 | ||||
-rw-r--r-- | test/functional/api/window_spec.lua | 14 |
20 files changed, 716 insertions, 185 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 0d85d6b539..fe69d9076f 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -993,7 +993,7 @@ nvim_get_option_info({name}) *nvim_get_option_info()* Resulting dictionary has keys: • name: Name of the option (like 'filetype') • shortname: Shortened name of the option (like 'ft') - • type: type of option ("string", "integer" or "boolean") + • type: type of option ("string", "number" or "boolean") • default: The default value for the option • was_set: Whether the option was set. • last_set_sid: Last set script id (if any) @@ -1299,6 +1299,17 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()* an external top-level window. Currently accepts no other positioning configuration together with this. + • `zindex`: Stacking order. floats with higher`zindex`go on top on floats with lower indices. Must + be larger than zero. The following screen + elements have hard-coded z-indices: + • 100: insert completion popupmenu + • 200: message scrollback + • 250: cmdline completion popupmenu (when + wildoptions+=pum) The default value for + floats are 50. In general, values below 100 + are recommended, unless there is a good + reason to overshadow builtin elements. + • `style`: Configure the appearance of the window. Currently only takes one non-empty value: • "minimal" Nvim will display the window with @@ -1705,6 +1716,12 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()* |nvim_get_hl_by_name|. in addition the following keys are also recognized: `default` : don't override existing definition, like `hi default` + `ctermfg` : sets foreground of cterm color + `ctermbg` : sets background of cterm color + `cterm` : cterm attribute map. sets attributed + for cterm colors. similer to `hi cterm` Note: by + default cterm attributes are same as attributes + of gui color nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()* Sets a global |mapping| for the given mode. @@ -2269,7 +2286,12 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts}) • "overlay": display over the specified column, without shifting the underlying text. + • "right_align": display right aligned in the + window. + • virt_text_win_col : position the virtual text + at a fixed window column (starting from the + first text column) • virt_text_hide : hide the virtual text when the background text is selected or hidden due to horizontal scroll 'nowrap' diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index b2e910a834..924401be74 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1091,6 +1091,11 @@ inside of strings can change! Also see 'softtabstop' option. > Using the mouse only works when 'mouse' contains 'n' or 'a'. +["x]zp or *zp* *zP* +["x]zP Like "p" and "P", except without adding trailing spaces + when pasting a block. Thus the inserted text will not + always be a rectangle. + You can use these commands to copy text from one place to another. Do this by first getting the text into a register with a yank, delete or change command, then inserting the register contents with a put command. You can @@ -1130,6 +1135,9 @@ a register, a paste on a visual selected area will paste that single line on each of the selected lines (thus replacing the blockwise selected region by a block of the pasted line). +Use |zP|/|zp| to paste a blockwise yanked register without appending trailing +spaces. + *blockwise-register* If you use a blockwise Visual mode command to get the text into the register, the block of text will be inserted before ("P") or after ("p") the cursor diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index e7d891bc33..17258f896d 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -851,6 +851,8 @@ tag char note action in Normal mode ~ |zm| zm subtract one from 'foldlevel' |zn| zn reset 'foldenable' |zo| zo open fold +|zp| zp paste in block-mode without trailing spaces +|zP| zP paste in block-mode without trailing spaces |zr| zr add one to 'foldlevel' |zs| zs when 'wrap' off scroll horizontally to position the cursor at the start (left diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 5c2ee568c5..d5e8db4dd7 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -619,15 +619,15 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result. Parameters are the same as |vim.lsp.buf_request()| but the return result is different. - Wait maximum of {timeout_ms} (default 100) ms. + Wait maximum of {timeout_ms} (default 1000) ms. Parameters: ~ {bufnr} (number) Buffer handle, or 0 for current. {method} (string) LSP method name {params} (optional, table) Parameters to send to the server - {timeout_ms} (optional, number, default=100) Maximum time - in milliseconds to wait for a result. + {timeout_ms} (optional, number, default=1000) Maximum + time in milliseconds to wait for a result. Return: ~ Map of client_id:request_result. On timeout, cancel or @@ -651,6 +651,14 @@ client() *vim.lsp.client* {status} is `true` , the function returns {request_id} as the second result. You can use this with `client.cancel_request(request_id)` to cancel the request. + • request_sync(method, params, timeout_ms, bufnr) Sends a + request to the server and synchronously waits for the + response. This is a wrapper around {client.request} + Returns: { err=err, result=result }, a dictionary, where + `err` and `result` come from the |lsp-handler|. On + timeout, cancel or error, returns `(nil, err)` where `err` + is a string describing the failure reason. If the request + was unsuccessful returns `nil` . • notify(method, params) Sends a notification to an LSP server. Returns: a boolean to indicate if the notification was successful. If it is false, then it will always be @@ -939,6 +947,9 @@ add_workspace_folder({workspace_folder}) not provided, the user will be prompted for a path using |input()|. +call_hierarchy({method}) *vim.lsp.buf.call_hierarchy()* + TODO: Documentation + clear_references() *vim.lsp.buf.clear_references()* Removes document highlights from current buffer. @@ -1017,6 +1028,32 @@ formatting({options}) *vim.lsp.buf.formatting()* See also: ~ https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting + *vim.lsp.buf.formatting_seq_sync()* +formatting_seq_sync({options}, {timeout_ms}, {order}) + Formats the current buffer by sequentially requesting + formatting from attached clients. + + Useful when multiple clients with formatting capability are + attached. + + Since it's synchronous, can be used for running on save, to + make sure buffer is formatted prior to being saved. + {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method. Example: > + + vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]] +< + + Parameters: ~ + {options} (optional, table) `FormattingOptions` + entries + {timeout_ms} (optional, number) Request timeout + {order} (optional, table) List of client names. + Formatting is requested from clients in the + following order: first all clients that are + not in the `order` list, then the remaining + clients in the order as they occur in the + `order` list. + *vim.lsp.buf.formatting_sync()* formatting_sync({options}, {timeout_ms}) Performs |vim.lsp.buf.formatting()| synchronously. @@ -1033,6 +1070,9 @@ formatting_sync({options}, {timeout_ms}) {options} Table with valid `FormattingOptions` entries {timeout_ms} (number) Request timeout + See also: ~ + |vim.lsp.buf.formatting_seq_sync| + hover() *vim.lsp.buf.hover()* Displays hover information about the symbol under the cursor in a floating window. Calling the function twice will jump @@ -1585,6 +1625,9 @@ buf_highlight_references({bufnr}, {references}) {references} List of `DocumentHighlight` objects to highlight +buf_lines({bufnr}) *vim.lsp.util.buf_lines()* + TODO: Documentation + character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()* Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. @@ -1650,12 +1693,15 @@ convert_input_to_markdown_lines({input}, {contents}) https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover *vim.lsp.util.convert_signature_help_to_markdown_lines()* -convert_signature_help_to_markdown_lines({signature_help}) +convert_signature_help_to_markdown_lines({signature_help}, {ft}) Converts `textDocument/SignatureHelp` response to markdown lines. Parameters: ~ {signature_help} Response of `textDocument/SignatureHelp` + {ft} optional filetype that will be use as + the `lang` for the label markdown code + block Return: ~ list of lines of converted markdown. @@ -1683,65 +1729,6 @@ extract_completion_items({result}) See also: ~ https://microsoft.github.io/language-server-protocol/specification#textDocument_completion - *vim.lsp.util.fancy_floating_markdown()* -fancy_floating_markdown({contents}, {opts}) - Converts markdown into syntax highlighted regions by stripping - the code blocks and converting them into highlighted code. - This will by default insert a blank line separator after those - code block regions to improve readability. The result is shown - in a floating preview. - - Parameters: ~ - {contents} table of lines to show in window - {opts} dictionary with optional fields - • height of floating window - • width of floating window - • wrap_at character to wrap at for computing - height - • max_width maximal width of floating window - • max_height maximal height of floating window - • pad_left number of columns to pad contents - at left - • pad_right number of columns to pad contents - at right - • pad_top number of lines to pad contents at - top - • pad_bottom number of lines to pad contents - at bottom - • separator insert separator after code block - - Return: ~ - width,height size of float - -focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()* - Parameters: ~ - {unique_name} (string) Window variable - {fn} (function) should return create a new - window and return a tuple of - ({focusable_buffer_id}, {window_id}). if - {focusable_buffer_id} is a valid buffer id, - the newly created window will be the new - focus associated with the current buffer - via the tag `unique_name` . - - Return: ~ - (pbufnr, pwinnr) if `fn()` has created a new window; nil - otherwise - - *vim.lsp.util.focusable_preview()* -focusable_preview({unique_name}, {fn}) - Focuses/unfocuses the floating preview window associated with - the current buffer via the window variable `unique_name` . If - no such preview window exists, makes a new one. - - Parameters: ~ - {unique_name} (string) Window variable - {fn} (function) The return values of this - function will be passed directly to - |vim.lsp.util.open_floating_preview()|, in - the case that a new floating window should - be created - get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* Returns visual width of tabstop. @@ -1755,6 +1742,22 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* See also: ~ |softtabstop| +get_line({uri}, {row}) *vim.lsp.util.get_line()* + Parameters: ~ + {uri} string uri of the resource to get the line from + {row} number zero-indexed line number + + Return: ~ + string the line at row in filename + +get_lines({uri}, {rows}) *vim.lsp.util.get_lines()* + Parameters: ~ + {uri} string uri of the resource to get the lines from + {rows} number[] zero-indexed line numbers + + Return: ~ + table<number string> a table mapping rows to lines + get_progress_messages() *vim.lsp.util.get_progress_messages()* TODO: Documentation @@ -1897,7 +1900,7 @@ parse_snippet({input}) *vim.lsp.util.parse_snippet()* Return: ~ (string) parsed snippet -preview_location({location}) *vim.lsp.util.preview_location()* +preview_location({location}, {opts}) *vim.lsp.util.preview_location()* Previews a location in a floating window behavior depends on type of location: @@ -1946,6 +1949,41 @@ set_qflist({items}) *vim.lsp.util.set_qflist()* Parameters: ~ {items} (table) list of items + *vim.lsp.util.stylize_markdown()* +stylize_markdown({bufnr}, {contents}, {opts}) + Converts markdown into syntax highlighted regions by stripping + the code blocks and converting them into highlighted code. + This will by default insert a blank line separator after those + code block regions to improve readability. + + This method configures the given buffer and returns the lines + to set. + + If you want to open a popup with fancy markdown, use + `open_floating_preview` instead + + Parameters: ~ + {contents} table of lines to show in window + {opts} dictionary with optional fields + • height of floating window + • width of floating window + • wrap_at character to wrap at for computing + height + • max_width maximal width of floating window + • max_height maximal height of floating window + • pad_left number of columns to pad contents + at left + • pad_right number of columns to pad contents + at right + • pad_top number of lines to pad contents at + top + • pad_bottom number of lines to pad contents + at bottom + • separator insert separator after code block + + Return: ~ + width,height size of float + symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()* Converts symbols to quickfix list items. diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index c3893d05c0..05404a2e35 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -650,42 +650,6 @@ vim.empty_dict() *vim.empty_dict()* Note: if numeric keys are added to the table, the metatable will be ignored and the dict converted to a list/array anyway. -vim.region({bufnr}, {pos1}, {pos2}, {type}, {inclusive}) *vim.region()* - Converts a selection specified by the buffer ({bufnr}), starting - position ({pos1}, a zero-indexed pair `{line1,column1}`), ending - position ({pos2}, same format as {pos1}), the type of the register - for the selection ({type}, see |regtype|), and a boolean indicating - whether the selection is inclusive or not, into a zero-indexed table - of linewise selections of the form `{linenr = {startcol, endcol}}` . - - *vim.register_keystroke_callback()* -vim.register_keystroke_callback({fn}, {ns_id}) - Register a lua {fn} with an {ns_id} to be run after every keystroke. - - Parameters: ~ - {fn}: (function): Function to call on keystroke. - It should take one argument, which is a string. - The string will contain the literal keys typed. - See |i_CTRL-V| - - If {fn} is `nil`, it removes the callback for the - associated {ns_id}. - - {ns_id}: (number) Namespace ID. If not passed or 0, will generate - and return a new namespace ID from |nvim_create_namespace()| - - Return: ~ - (number) Namespace ID associated with {fn} - - NOTE: {fn} will be automatically removed if an error occurs while - calling. This is to prevent the annoying situation of every keystroke - erroring while trying to remove a broken callback. - - NOTE: {fn} will receive the keystrokes after mappings have been - evaluated - - NOTE: {fn} will *NOT* be cleared from |nvim_buf_clear_namespace()| - vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()* Sends {event} to {channel} via |RPC| and returns immediately. If {channel} is 0, the event is broadcast to all channels. @@ -1118,6 +1082,23 @@ vim.wo *vim.wo* ============================================================================== Lua module: vim *lua-vim* +defer_fn({fn}, {timeout}) *vim.defer_fn()* + Defers calling `fn` until `timeout` ms passes. + + Use to do a one-shot timer that calls `fn` Note: The {fn} is |schedule_wrap|ped automatically, so API + functions are safe to call. + + Parameters: ~ + {fn} Callback to call once `timeout` expires + {timeout} Number of milliseconds to wait before calling + `fn` + + Return: ~ + timer luv timer object + +insert_keys({obj}) *vim.insert_keys()* + TODO: Documentation + inspect({object}, {options}) *vim.inspect()* Return a human-readable representation of the given object. @@ -1125,9 +1106,19 @@ inspect({object}, {options}) *vim.inspect()* https://github.com/kikito/inspect.lua https://github.com/mpeterv/vinspect -make_meta_accessor({get}, {set}, {del}) *vim.make_meta_accessor()* +make_dict_accessor({scope}) *vim.make_dict_accessor()* TODO: Documentation +notify({msg}, {log_level}, {_opts}) *vim.notify()* + Notification provider without a runtime, writes to :Messages + + Parameters: ~ + {msg} Content of the notification to show to the + user + {log_level} Optional log level + {opts} Dictionary with optional options (timeout, + etc) + paste({lines}, {phase}) *vim.paste()* Paste handler, invoked by |nvim_paste()| when a conforming UI (such as the |TUI|) pastes text into the editor. @@ -1160,6 +1151,53 @@ paste({lines}, {phase}) *vim.paste()* See also: ~ |paste| +region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()* + Get a table of lines with start, end columns for a region + marked by two points + + Parameters: ~ + {bufnr} number of buffer + {pos1} (line, column) tuple marking beginning of + region + {pos2} (line, column) tuple marking end of region + {regtype} type of selection (:help setreg) + {inclusive} boolean indicating whether the selection is + end-inclusive + + Return: ~ + region lua table of the form {linenr = {startcol,endcol}} + + *vim.register_keystroke_callback()* +register_keystroke_callback({fn}, {ns_id}) + Register a lua {fn} with an {id} to be run after every + keystroke. + + If {fn} is nil, it removes the callback for the associated + {ns_id} + Note: + {fn} will not be cleared from |nvim_buf_clear_namespace()| + + Note: + {fn} will receive the keystrokes after mappings have been + evaluated + + Parameters: ~ + {fn} function: Function to call. It should take one + argument, which is a string. The string will contain + the literal keys typed. See |i_CTRL-V| + {ns_id} number? Namespace ID. If not passed or 0, will + generate and return a new namespace ID from + |nvim_create_namesapce()| + + Return: ~ + number Namespace ID associated with {fn} + + Note: + {fn} will be automatically removed if an error occurs + while calling. This is to prevent the annoying situation + of every keystroke erroring while trying to remove a + broken callback. + schedule_wrap({cb}) *vim.schedule_wrap()* Defers callback `cb` until the Nvim API is safe to call. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index a7b43cfa6b..0fa6518c0b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1462,12 +1462,33 @@ end) --@param row number zero-indexed line number --@return string the line at row in filename function M.get_line(uri, row) + return M.get_lines(uri, { row })[row] +end + +-- Gets the zero-indexed lines from the given uri. +-- For non-file uris, we load the buffer and get the lines. +-- If a loaded buffer exists, then that is used. +-- Otherwise we get the lines using libuv which is a lot faster than loading the buffer. +--@param uri string uri of the resource to get the lines from +--@param rows number[] zero-indexed line numbers +--@return table<number string> a table mapping rows to lines +function M.get_lines(uri, rows) + rows = type(rows) == "table" and rows or { rows } + + local function buf_lines(bufnr) + local lines = {} + for _, row in pairs(rows) do + lines[row] = (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1] + end + return lines + end + -- load the buffer if this is not a file uri -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds. if uri:sub(1, 4) ~= "file" then local bufnr = vim.uri_to_bufnr(uri) vim.fn.bufload(bufnr) - return (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1] + return buf_lines(bufnr) end local filename = vim.uri_to_fname(uri) @@ -1475,22 +1496,44 @@ function M.get_line(uri, row) -- use loaded buffers if available if vim.fn.bufloaded(filename) == 1 then local bufnr = vim.fn.bufnr(filename, false) - return (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1] + return buf_lines(bufnr) end + -- get the data from the file local fd = uv.fs_open(filename, "r", 438) - -- TODO: what should we do in this case? if not fd then return "" end local stat = uv.fs_fstat(fd) local data = uv.fs_read(fd, stat.size, 0) uv.fs_close(fd) + local lines = {} -- rows we need to retrieve + local need = 0 -- keep track of how many unique rows we need + for _, row in pairs(rows) do + if not lines[row] then + need = need + 1 + end + lines[row] = true + end + + local found = 0 local lnum = 0 + for line in string.gmatch(data, "([^\n]*)\n?") do - if lnum == row then return line end + if lines[lnum] == true then + lines[lnum] = line + found = found + 1 + if found == need then break end + end lnum = lnum + 1 end - return "" + + -- change any lines we didn't find to the empty string + for i, line in pairs(lines) do + if line == true then + lines[i] = "" + end + end + return lines end --- Returns the items with the byte position calculated correctly and in sorted @@ -1522,10 +1565,22 @@ function M.locations_to_items(locations) local rows = grouped[uri] table.sort(rows, position_sort) local filename = vim.uri_to_fname(uri) + + -- list of row numbers + local uri_rows = {} + for _, temp in ipairs(rows) do + local pos = temp.start + local row = pos.line + table.insert(uri_rows, row) + end + + -- get all the lines for this uri + local lines = M.get_lines(uri, uri_rows) + for _, temp in ipairs(rows) do local pos = temp.start local row = pos.line - local line = M.get_line(uri, row) + local line = lines[row] or "" local col = pos.character table.insert(items, { filename = filename, diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 5abdc33709..4b1c2d4baa 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,7 +1645,7 @@ bool api_object_to_bool(Object obj, const char *what, } else if (obj.type == kObjectTypeNil) { return nil_value; // caller decides what NIL (missing retval in lua) means } else { - api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what); + api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what); return false; } } @@ -1868,7 +1868,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) } bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, - Error *err) + bool new_win, Error *err) { // TODO(bfredl): use a get/has_key interface instead and get rid of extra // flags @@ -1968,24 +1968,15 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, } has_bufpos = true; } else if (!strcmp(key, "external")) { - if (val.type == kObjectTypeInteger) { - fconfig->external = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->external = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'external' key must be Boolean"); + has_external = fconfig->external + = api_object_to_bool(val, "'external' key", false, err); + if (ERROR_SET(err)) { return false; } - has_external = fconfig->external; } else if (!strcmp(key, "focusable")) { - if (val.type == kObjectTypeInteger) { - fconfig->focusable = val.data.integer; - } else if (val.type == kObjectTypeBoolean) { - fconfig->focusable = val.data.boolean; - } else { - api_set_error(err, kErrorTypeValidation, - "'focusable' key must be Boolean"); + fconfig->focusable + = api_object_to_bool(val, "'focusable' key", true, err); + if (ERROR_SET(err)) { return false; } } else if (strequal(key, "zindex")) { @@ -2015,6 +2006,12 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key"); } + } else if (strequal(key, "noautocmd") && new_win) { + fconfig->noautocmd + = api_object_to_bool(val, "'noautocmd' key", false, err); + if (ERROR_SET(err)) { + return false; + } } else { api_set_error(err, kErrorTypeValidation, "Invalid key '%s'", key); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index a76cefe294..99a41f4f6f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1459,6 +1459,9 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// By default `FloatBorder` highlight is used which links to `VertSplit` /// when not defined. It could also be specified by character: /// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// - `noautocmd`: If true then no buffer-related autocommand events such as +/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from +/// calling this function. /// /// @param[out] err Error details, if any /// @@ -1469,7 +1472,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, FUNC_API_CHECK_TEXTLOCK { FloatConfig fconfig = FLOAT_CONFIG_INIT; - if (!parse_float_config(config, &fconfig, false, err)) { + if (!parse_float_config(config, &fconfig, false, true, err)) { return 0; } win_T *wp = win_new_float(NULL, fconfig, err); @@ -1484,7 +1487,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, return 0; } if (buffer > 0) { - nvim_win_set_buf(wp->handle, buffer, err); + win_set_buf(wp->handle, buffer, fconfig.noautocmd, err); } if (fconfig.style == kWinStyleMinimal) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 158e149628..a26213af98 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -46,35 +46,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) FUNC_API_SINCE(5) FUNC_API_CHECK_TEXTLOCK { - win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; - buf_T *buf = find_buffer_by_handle(buffer, err); - tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; - - if (!win || !buf) { - return; - } - - if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to window %d", - window); - } - - try_start(); - int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); - if (!try_end(err) && result == FAIL) { - api_set_error(err, - kErrorTypeException, - "Failed to set buffer %d", - buffer); - } - - // If window is not current, state logic will not validate its cursor. - // So do it now. - validate_cursor(); - - restore_win_noblock(save_curwin, save_curtab, false); + win_set_buf(window, buffer, false, err); } /// Gets the (1,0)-indexed cursor position in the window. |api-indexing| @@ -423,7 +395,7 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err) // reuse old values, if not overriden FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config; - if (!parse_float_config(config, &fconfig, !new_float, err)) { + if (!parse_float_config(config, &fconfig, !new_float, false, err)) { return; } if (new_float) { diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 0c839ba12a..e3e538bd12 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1090,6 +1090,7 @@ typedef struct { schar_T border_chars[8]; int border_hl_ids[8]; int border_attr[8]; + bool noautocmd; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -1098,7 +1099,8 @@ typedef struct { .relative = 0, .external = false, \ .focusable = true, \ .zindex = kZIndexFloatDefault, \ - .style = kWinStyleUnused }) + .style = kWinStyleUnused, \ + .noautocmd = false }) // Structure to store last cursor position and topline. Used by check_lnums() // and reset_lnums(). diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 13706fb14a..173d8d46d1 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -4384,6 +4384,12 @@ dozet: } break; + // "zp", "zP" in block mode put without addind trailing spaces + case 'P': + case 'p': + nv_put(cap); + break; + /* "zF": create fold command */ /* "zf": create fold operator */ case 'F': @@ -7913,12 +7919,14 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) flags |= PUT_FIXINDENT; } else { dir = (cap->cmdchar == 'P' - || (cap->cmdchar == 'g' && cap->nchar == 'P')) - ? BACKWARD : FORWARD; + || ((cap->cmdchar == 'g' || cap->cmdchar == 'z') + && cap->nchar == 'P')) ? BACKWARD : FORWARD; } prep_redo_cmd(cap); if (cap->cmdchar == 'g') { flags |= PUT_CURSEND; + } else if (cap->cmdchar == 'z') { + flags |= PUT_BLOCK_INNER; } if (VIsual_active) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 0ed116c17f..2c8c7f0567 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2788,13 +2788,13 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) recursive = false; } -/* - * Put contents of register "regname" into the text. - * Caller must check "regname" to be valid! - * "flags": PUT_FIXINDENT make indent look nice - * PUT_CURSEND leave cursor after end of new text - * PUT_LINE force linewise put (":put") - dir: BACKWARD for 'P', FORWARD for 'p' */ +// Put contents of register "regname" into the text. +// Caller must check "regname" to be valid! +// "flags": PUT_FIXINDENT make indent look nice +// PUT_CURSEND leave cursor after end of new text +// PUT_LINE force linewise put (":put") +// PUT_BLOCK_INNER in block mode, do not add trailing spaces +// dir: BACKWARD for 'P', FORWARD for 'p' void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) { char_u *ptr; @@ -3126,7 +3126,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.coladd = 0; bd.textcol = 0; for (i = 0; i < y_size; i++) { - int spaces; + int spaces = 0; char shortline; // can just be 0 or 1, needed for blockwise paste beyond the current // buffer end @@ -3177,13 +3177,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) yanklen = (int)STRLEN(y_array[i]); - // calculate number of spaces required to fill right side of block - spaces = y_width + 1; - for (long j = 0; j < yanklen; j++) { - spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); - } - if (spaces < 0) { - spaces = 0; + if ((flags & PUT_BLOCK_INNER) == 0) { + // calculate number of spaces required to fill right side of + // block + spaces = y_width + 1; + for (int j = 0; j < yanklen; j++) { + spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0); + } + if (spaces < 0) { + spaces = 0; + } } // insert the new text diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 77d6b4435f..112ffbeaba 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -14,13 +14,14 @@ typedef int (*Indenter)(void); -/* flags for do_put() */ -#define PUT_FIXINDENT 1 /* make indent look nice */ -#define PUT_CURSEND 2 /* leave cursor after end of new text */ -#define PUT_CURSLINE 4 /* leave cursor on last line of new text */ -#define PUT_LINE 8 /* put register as lines */ -#define PUT_LINE_SPLIT 16 /* split line for linewise register */ -#define PUT_LINE_FORWARD 32 /* put linewise register below Visual sel. */ +// flags for do_put() +#define PUT_FIXINDENT 1 // make indent look nice +#define PUT_CURSEND 2 // leave cursor after end of new text +#define PUT_CURSLINE 4 // leave cursor on last line of new text +#define PUT_LINE 8 // put register as lines +#define PUT_LINE_SPLIT 16 // split line for linewise register +#define PUT_LINE_FORWARD 32 // put linewise register below Visual sel. +#define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces /* * Registers: diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index accf9b0bb5..6b84bb3207 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -3755,6 +3755,7 @@ static bool reg_match_visual(void) int mode; colnr_T start, end; colnr_T start2, end2; + colnr_T curswant; // Check if the buffer is the current buffer. if (rex.reg_buf != curbuf || VIsual.lnum == 0) { @@ -3770,6 +3771,7 @@ static bool reg_match_visual(void) bot = VIsual; } mode = VIsual_mode; + curswant = wp->w_curswant; } else { if (lt(curbuf->b_visual.vi_start, curbuf->b_visual.vi_end)) { top = curbuf->b_visual.vi_start; @@ -3779,6 +3781,7 @@ static bool reg_match_visual(void) bot = curbuf->b_visual.vi_start; } mode = curbuf->b_visual.vi_mode; + curswant = curbuf->b_visual.vi_curswant; } lnum = rex.lnum + rex.reg_firstlnum; if (lnum < top.lnum || lnum > bot.lnum) { @@ -3798,8 +3801,9 @@ static bool reg_match_visual(void) start = start2; if (end2 > end) end = end2; - if (top.col == MAXCOL || bot.col == MAXCOL) + if (top.col == MAXCOL || bot.col == MAXCOL || curswant == MAXCOL) { end = MAXCOL; + } unsigned int cols_u = win_linetabsize(wp, rex.line, (colnr_T)(rex.input - rex.line)); assert(cols_u <= MAXCOL); diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim index 527224ccd2..180524cd73 100644 --- a/src/nvim/testdir/test_blockedit.vim +++ b/src/nvim/testdir/test_blockedit.vim @@ -1,6 +1,5 @@ " Test for block inserting " -" TODO: rewrite test39.in into this new style test func Test_blockinsert_indent() new diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 7a846e5ea0..4a00999c45 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -512,6 +512,12 @@ func Test_normal14_page_eol() bw! endfunc +" Test for errors with z command +func Test_normal_z_error() + call assert_beeps('normal! z2p') + call assert_beeps('normal! zq') +endfunc + func Test_normal15_z_scroll_vert() " basic test for z commands that scroll the window call Setup_NewWindow() @@ -2877,3 +2883,35 @@ func Test_normal_gk() bw! set cpoptions& number& numberwidth& endfunc + +" Some commands like yy, cc, dd, >>, << and !! accept a count after +" typing the first letter of the command. +func Test_normal_count_after_operator() + new + setlocal shiftwidth=4 tabstop=8 autoindent + call setline(1, ['one', 'two', 'three', 'four', 'five']) + let @a = '' + normal! j"ay4y + call assert_equal("two\nthree\nfour\nfive\n", @a) + normal! 3G>2> + call assert_equal(['one', 'two', ' three', ' four', 'five'], + \ getline(1, '$')) + exe "normal! 3G0c2cred\nblue" + call assert_equal(['one', 'two', ' red', ' blue', 'five'], + \ getline(1, '$')) + exe "normal! gg<8<" + call assert_equal(['one', 'two', 'red', 'blue', 'five'], + \ getline(1, '$')) + exe "normal! ggd3d" + call assert_equal(['blue', 'five'], getline(1, '$')) + call setline(1, range(1, 4)) + call feedkeys("gg!3!\<C-B>\"\<CR>", 'xt') + call assert_equal('".,.+2!', @:) + call feedkeys("gg!1!\<C-B>\"\<CR>", 'xt') + call assert_equal('".!', @:) + call feedkeys("gg!9!\<C-B>\"\<CR>", 'xt') + call assert_equal('".,$!', @:) + bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index b391663e0f..5ba24d047c 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -824,6 +824,26 @@ func Test_incsearch_search_dump() call delete('Xis_search_script') endfunc +func Test_hlsearch_block_visual_match() + CheckScreendump + + let lines =<< trim END + set hlsearch + call setline(1, ['aa', 'bbbb', 'cccccc']) + END + call writefile(lines, 'Xhlsearch_block') + let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60}) + + call term_sendkeys(buf, "G\<C-V>$kk\<Esc>") + sleep 100m + call term_sendkeys(buf, "/\\%V\<CR>") + sleep 100m + call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {}) + + call StopVimInTerminal(buf) + call delete('Xhlsearch_block') +endfunc + func Test_incsearch_substitute() CheckFunction test_override CheckOption incsearch diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 73c7960579..21fd57b791 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1,4 +1,4 @@ -" Tests for various Visual mode. +" Tests for various Visual modes. func Test_block_shift_multibyte() " Uses double-wide character. @@ -738,4 +738,272 @@ func Test_select_mode_gv() bwipe! endfunc +" Tests for the visual block mode commands +func Test_visual_block_mode() + new + call append(0, '') + call setline(1, ['abcdefghijklm', 'abcdefghijklm', 'abcdefghijklm', + \ 'abcdefghijklm', 'abcdefghijklm']) + call cursor(1, 1) + + " Test shift-right of a block + exe "normal jllll\<C-V>jj>wll\<C-V>jlll>" + " Test shift-left of a block + exe "normal G$hhhh\<C-V>kk<" + " Test block-insert + exe "normal Gkl\<C-V>kkkIxyz" + " Test block-replace + exe "normal Gllll\<C-V>kkklllrq" + " Test block-change + exe "normal G$khhh\<C-V>hhkkcmno" + call assert_equal(['axyzbcdefghijklm', + \ 'axyzqqqq mno ghijklm', + \ 'axyzqqqqef mno ghijklm', + \ 'axyzqqqqefgmnoklm', + \ 'abcdqqqqijklm'], getline(1, 5)) + + " Test from ':help v_b_I_example' + %d _ + setlocal tabstop=8 shiftwidth=4 + let lines =<< trim END + abcdefghijklmnopqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmnopqrstuvwxyz + END + call setline(1, lines) + exe "normal ggfo\<C-V>3jISTRING" + let expected =<< trim END + abcdefghijklmnSTRINGopqrstuvwxyz + abc STRING defghijklmnopqrstuvwxyz + abcdef ghi STRING jklmnopqrstuvwxyz + abcdefghijklmnSTRINGopqrstuvwxyz + END + call assert_equal(expected, getline(1, '$')) + + " Test from ':help v_b_A_example' + %d _ + let lines =<< trim END + abcdefghijklmnopqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmnopqrstuvwxyz + END + call setline(1, lines) + exe "normal ggfo\<C-V>3j$ASTRING" + let expected =<< trim END + abcdefghijklmnopqrstuvwxyzSTRING + abc defghijklmnopqrstuvwxyzSTRING + abcdef ghi jklmnopqrstuvwxyzSTRING + abcdefghijklmnopqrstuvwxyzSTRING + END + call assert_equal(expected, getline(1, '$')) + + " Test from ':help v_b_<_example' + %d _ + let lines =<< trim END + abcdefghijklmnopqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmnopqrstuvwxyz + END + call setline(1, lines) + exe "normal ggfo\<C-V>3j3l<.." + let expected =<< trim END + abcdefghijklmnopqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmnopqrstuvwxyz + END + call assert_equal(expected, getline(1, '$')) + + " Test from ':help v_b_>_example' + %d _ + let lines =<< trim END + abcdefghijklmnopqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmnopqrstuvwxyz + END + call setline(1, lines) + exe "normal ggfo\<C-V>3j>.." + let expected =<< trim END + abcdefghijklmn opqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmn opqrstuvwxyz + END + call assert_equal(expected, getline(1, '$')) + + " Test from ':help v_b_r_example' + %d _ + let lines =<< trim END + abcdefghijklmnopqrstuvwxyz + abc defghijklmnopqrstuvwxyz + abcdef ghi jklmnopqrstuvwxyz + abcdefghijklmnopqrstuvwxyz + END + call setline(1, lines) + exe "normal ggfo\<C-V>5l3jrX" + let expected =<< trim END + abcdefghijklmnXXXXXXuvwxyz + abc XXXXXXhijklmnopqrstuvwxyz + abcdef ghi XXXXXX jklmnopqrstuvwxyz + abcdefghijklmnXXXXXXuvwxyz + END + call assert_equal(expected, getline(1, '$')) + + bwipe! + set tabstop& shiftwidth& +endfunc + +" Test block-insert using cursor keys for movement +func Test_visual_block_insert_cursor_keys() + new + call append(0, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd']) + call cursor(1, 1) + + exe "norm! l\<C-V>jjjlllI\<Right>\<Right> \<Esc>" + call assert_equal(['aaa aaa', 'bbb bbb', 'ccc ccc', 'ddd ddd'], + \ getline(1, 4)) + + call deletebufline('', 1, '$') + call setline(1, ['xaaa', 'bbbb', 'cccc', 'dddd']) + call cursor(1, 1) + exe "norm! \<C-V>jjjI<>\<Left>p\<Esc>" + call assert_equal(['<p>xaaa', '<p>bbbb', '<p>cccc', '<p>dddd'], + \ getline(1, 4)) + bwipe! +endfunc + +func Test_visual_block_create() + new + call append(0, '') + " Test for Visual block was created with the last <C-v>$ + call setline(1, ['A23', '4567']) + call cursor(1, 1) + exe "norm! l\<C-V>j$Aab\<Esc>" + call assert_equal(['A23ab', '4567ab'], getline(1, 2)) + + " Test for Visual block was created with the middle <C-v>$ (1) + call deletebufline('', 1, '$') + call setline(1, ['B23', '4567']) + call cursor(1, 1) + exe "norm! l\<C-V>j$hAab\<Esc>" + call assert_equal(['B23 ab', '4567ab'], getline(1, 2)) + + " Test for Visual block was created with the middle <C-v>$ (2) + call deletebufline('', 1, '$') + call setline(1, ['C23', '4567']) + call cursor(1, 1) + exe "norm! l\<C-V>j$hhAab\<Esc>" + call assert_equal(['C23ab', '456ab7'], getline(1, 2)) + bwipe! +endfunc + +" Test for Visual block insert when virtualedit=all +func Test_virtualedit_visual_block() + set ve=all + new + call append(0, ["\t\tline1", "\t\tline2", "\t\tline3"]) + call cursor(1, 1) + exe "norm! 07l\<C-V>jjIx\<Esc>" + call assert_equal([" x \tline1", + \ " x \tline2", + \ " x \tline3"], getline(1, 3)) + + " Test for Visual block append when virtualedit=all + exe "norm! 012l\<C-v>jjAx\<Esc>" + call assert_equal([' x x line1', + \ ' x x line2', + \ ' x x line3'], getline(1, 3)) + set ve= + bwipe! +endfunc + +" Test for changing case +func Test_visual_change_case() + new + " gUe must uppercase a whole word, also when ß changes to SS + exe "normal Gothe youtußeuu end\<Esc>Ypk0wgUe\r" + " gUfx must uppercase until x, inclusive. + exe "normal O- youßtußexu -\<Esc>0fogUfx\r" + " VU must uppercase a whole line + exe "normal YpkVU\r" + " same, when it's the last line in the buffer + exe "normal YPGi111\<Esc>VUddP\r" + " Uppercase two lines + exe "normal Oblah di\rdoh dut\<Esc>VkUj\r" + " Uppercase part of two lines + exe "normal ddppi333\<Esc>k0i222\<Esc>fyllvjfuUk" + call assert_equal(['the YOUTUSSEUU end', '- yOUSSTUSSEXu -', + \ 'THE YOUTUSSEUU END', '111THE YOUTUSSEUU END', 'BLAH DI', 'DOH DUT', + \ '222the yoUTUSSEUU END', '333THE YOUTUßeuu end'], getline(2, '$')) + bwipe! +endfunc + +" Test for Visual replace using Enter or NL +func Test_visual_replace_crnl() + new + exe "normal G3o123456789\e2k05l\<C-V>2jr\r" + exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\r\n" + exe "normal G3o123456789\e2k05l\<C-V>2jr\n" + exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\n" + call assert_equal(['12345', '789', '12345', '789', '12345', '789', "98\r65", + \ "98\r65", "98\r65", '12345', '789', '12345', '789', '12345', '789', + \ "98\n65", "98\n65", "98\n65"], getline(2, '$')) + bwipe! +endfunc + +func Test_ve_block_curpos() + new + " Test cursor position. When ve=block and Visual block mode and $gj + call append(0, ['12345', '789']) + call cursor(1, 3) + set virtualedit=block + exe "norm! \<C-V>$gj\<Esc>" + call assert_equal([0, 2, 4, 0], getpos("'>")) + set virtualedit= + bwipe! +endfunc + +" Test for block_insert when replacing spaces in front of the a with tabs +func Test_block_insert_replace_tabs() + new + set ts=8 sts=4 sw=4 + call append(0, ["#define BO_ALL\t 0x0001", + \ "#define BO_BS\t 0x0002", + \ "#define BO_CRSR\t 0x0004"]) + call cursor(1, 1) + exe "norm! f0\<C-V>2jI\<tab>\<esc>" + call assert_equal([ + \ "#define BO_ALL\t\t0x0001", + \ "#define BO_BS\t \t0x0002", + \ "#define BO_CRSR\t \t0x0004", ''], getline(1, '$')) + set ts& sts& sw& + bwipe! +endfunc + +func Test_visual_put_in_block_using_zp() + new + " paste using zP + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ '/subdir', + \ '/longsubdir', + \ '/longlongsubdir']) + exe "normal! 5G\<c-v>2j$y" + norm! 1Gf;zP + call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3)) + %d + " paste using zP + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ '/subdir', + \ '/longsubdir', + \ '/longlongsubdir']) + exe "normal! 5G\<c-v>2j$y" + norm! 1Gf;hzp + call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3)) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/window.c b/src/nvim/window.c index 936bfa8c5b..cdeae2e294 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -570,6 +570,45 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, } } +void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) +{ + win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; + buf_T *buf = find_buffer_by_handle(buffer, err); + tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; + + if (!win || !buf) { + return; + } + + if (noautocmd) { + block_autocmds(); + } + if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + api_set_error(err, + kErrorTypeException, + "Failed to switch to window %d", + window); + } + + try_start(); + int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); + if (!try_end(err) && result == FAIL) { + api_set_error(err, + kErrorTypeException, + "Failed to set buffer %d", + buffer); + } + + // If window is not current, state logic will not validate its cursor. + // So do it now. + validate_cursor(); + + restore_win_noblock(save_curwin, save_curtab, false); + if (noautocmd) { + unblock_autocmds(); + } +} + /// Create a new float. /// /// if wp == NULL allocate a new window, otherwise turn existing window into a diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index ceeb84cec9..a57826f7e7 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -387,4 +387,18 @@ describe('API/win', function() eq({oldbuf}, meths.list_bufs()) end) end) + + describe('open_win', function() + it('noautocmd option works', function() + command('autocmd BufEnter,BufLeave,BufWinEnter * let g:fired = 1') + meths.open_win(meths.create_buf(true, true), true, { + relative='win', row=3, col=3, width=12, height=3, noautocmd=true + }) + eq(0, funcs.exists('g:fired')) + meths.open_win(meths.create_buf(true, true), true, { + relative='win', row=3, col=3, width=12, height=3 + }) + eq(1, funcs.exists('g:fired')) + end) + end) end) |