aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-05-24 19:18:11 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-05-24 19:18:11 +0000
commitff7ed8f586589d620a806c3758fac4a47a8e7e15 (patch)
tree729bbcb92231538fa61dab6c3d890b025484b7f5 /src/nvim/api
parent376914f419eb08fdf4c1a63a77e1f035898a0f10 (diff)
parent28c04948a1c887a1cc0cb64de79fa32631700466 (diff)
downloadrneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.gz
rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.tar.bz2
rneovim-ff7ed8f586589d620a806c3758fac4a47a8e7e15.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/autocmd.c6
-rw-r--r--src/nvim/api/buffer.c68
-rw-r--r--src/nvim/api/deprecated.c109
-rw-r--r--src/nvim/api/extmark.c80
-rw-r--r--src/nvim/api/keysets_defs.h16
-rw-r--r--src/nvim/api/private/converter.c3
-rw-r--r--src/nvim/api/private/helpers.c8
-rw-r--r--src/nvim/api/tabpage.c6
-rw-r--r--src/nvim/api/ui.c61
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/api/vim.c355
-rw-r--r--src/nvim/api/win_config.c363
-rw-r--r--src/nvim/api/window.c12
13 files changed, 704 insertions, 387 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index d71bcc4bcf..ca8367b7ce 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -381,15 +381,15 @@ cleanup:
/// - desc (string) optional: description (for documentation and troubleshooting).
/// - callback (function|string) optional: Lua function (or Vimscript function name, if
/// string) called when the event(s) is triggered. Lua callback can return a truthy
-/// value (not `false` or `nil`) to delete the autocommand. Receives a table argument
-/// with these keys:
+/// value (not `false` or `nil`) to delete the autocommand. Receives one argument,
+/// a table with these keys: [event-args]()
/// - id: (number) autocommand id
/// - event: (string) name of the triggered event |autocmd-events|
/// - group: (number|nil) autocommand group id, if any
/// - match: (string) expanded value of [<amatch>]
/// - buf: (number) expanded value of [<abuf>]
/// - file: (string) expanded value of [<afile>]
-/// - data: (any) arbitrary data passed from [nvim_exec_autocmds()]
+/// - data: (any) arbitrary data passed from [nvim_exec_autocmds()] [event-data]()
/// - command (string) optional: Vim command to execute on event. Cannot be used with
/// {callback}
/// - once (boolean) optional: defaults to false. Run the autocommand
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 7f195de959..7e64808709 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -39,6 +39,7 @@
#include "nvim/memory_defs.h"
#include "nvim/move.h"
#include "nvim/ops.h"
+#include "nvim/option_vars.h"
#include "nvim/pos_defs.h"
#include "nvim/state_defs.h"
#include "nvim/types_defs.h"
@@ -229,20 +230,6 @@ Boolean nvim_buf_detach(uint64_t channel_id, Buffer buffer, Error *err)
return true;
}
-/// @nodoc
-void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *err)
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return;
- }
- if (last < 0) {
- last = buf->b_ml.ml_line_count;
- }
-
- redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
-}
-
/// Gets a line-range from the buffer.
///
/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
@@ -529,18 +516,18 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// Another call to ml_get_buf() may free the lines, so we make copies
char *str_at_start = ml_get_buf(buf, (linenr_T)start_row);
- size_t len_at_start = strlen(str_at_start);
- str_at_start = arena_memdupz(arena, str_at_start, len_at_start);
- start_col = start_col < 0 ? (int64_t)len_at_start + start_col + 1 : start_col;
- VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", {
+ colnr_T len_at_start = ml_get_buf_len(buf, (linenr_T)start_row);
+ str_at_start = arena_memdupz(arena, str_at_start, (size_t)len_at_start);
+ start_col = start_col < 0 ? len_at_start + start_col + 1 : start_col;
+ VALIDATE_RANGE((start_col >= 0 && start_col <= len_at_start), "start_col", {
return;
});
char *str_at_end = ml_get_buf(buf, (linenr_T)end_row);
- size_t len_at_end = strlen(str_at_end);
- str_at_end = arena_memdupz(arena, str_at_end, len_at_end);
- end_col = end_col < 0 ? (int64_t)len_at_end + end_col + 1 : end_col;
- VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", {
+ colnr_T len_at_end = ml_get_buf_len(buf, (linenr_T)end_row);
+ str_at_end = arena_memdupz(arena, str_at_end, (size_t)len_at_end);
+ end_col = end_col < 0 ? len_at_end + end_col + 1 : end_col;
+ VALIDATE_RANGE((end_col >= 0 && end_col <= len_at_end), "end_col", {
return;
});
@@ -563,12 +550,10 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
if (start_row == end_row) {
old_byte = (bcount_t)end_col - start_col;
} else {
- old_byte += (bcount_t)len_at_start - start_col;
+ old_byte += len_at_start - start_col;
for (int64_t i = 1; i < end_row - start_row; i++) {
int64_t lnum = start_row + i;
-
- const char *bufline = ml_get_buf(buf, (linenr_T)lnum);
- old_byte += (bcount_t)(strlen(bufline)) + 1;
+ old_byte += ml_get_buf_len(buf, (linenr_T)lnum) + 1;
}
old_byte += (bcount_t)end_col + 1;
}
@@ -577,7 +562,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
String last_item = replacement.items[replacement.size - 1].data.string;
size_t firstlen = (size_t)start_col + first_item.size;
- size_t last_part_len = len_at_end - (size_t)end_col;
+ size_t last_part_len = (size_t)len_at_end - (size_t)end_col;
if (replacement.size == 1) {
firstlen += last_part_len;
}
@@ -970,7 +955,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err)
return cstr_as_string(buf->b_ffname);
}
-/// Sets the full file name for a buffer
+/// Sets the full file name for a buffer, like |:file_f|
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param name Buffer name
@@ -986,12 +971,23 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err)
try_start();
+ const bool is_curbuf = buf == curbuf;
+ const int save_acd = p_acd;
+ if (!is_curbuf) {
+ // Temporarily disable 'autochdir' when setting file name for another buffer.
+ p_acd = false;
+ }
+
// Using aucmd_*: autocommands will be executed by rename_buffer
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
int ren_ret = rename_buffer(name.data);
aucmd_restbuf(&aco);
+ if (!is_curbuf) {
+ p_acd = save_acd;
+ }
+
if (try_end(err)) {
return;
}
@@ -1271,10 +1267,13 @@ static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra)
} else if (extra < 0) {
check_cursor_lnum(win);
}
- check_cursor_col_win(win);
+ check_cursor_col(win);
changed_cline_bef_curs(win);
+ win->w_valid &= ~(VALID_BOTLINE_AP);
+ update_topline(win);
+ } else {
+ invalidate_botline(win);
}
- invalidate_botline(win);
}
/// Fix cursor position after replacing text
@@ -1309,7 +1308,7 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l
// it's easier to work with a single value here.
// col and coladd are fixed by a later call
- // to check_cursor_col_win when necessary
+ // to check_cursor_col when necessary
win->w_cursor.col += win->w_cursor.coladd;
win->w_cursor.coladd = 0;
@@ -1324,7 +1323,7 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l
// it already (in case virtualedit is active)
// column might be additionally adjusted below
// to keep it inside col range if needed
- colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row));
+ colnr_T len = ml_get_buf_len(win->w_buffer, new_end_row);
if (win->w_cursor.col < len) {
win->w_cursor.col = len;
}
@@ -1345,7 +1344,7 @@ static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, l
}
}
- check_cursor_col_win(win);
+ check_cursor_col(win);
changed_cline_bef_curs(win);
invalidate_botline(win);
}
@@ -1424,6 +1423,7 @@ void buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool
for (size_t i = 0; i < n; i++) {
linenr_T lnum = start + (linenr_T)i;
char *bufstr = ml_get_buf(buf, lnum);
- push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl, arena);
+ size_t bufstrlen = (size_t)ml_get_buf_len(buf, lnum);
+ push_linestr(lstate, l, bufstr, bufstrlen, start_idx + (int)i, replace_nl, arena);
}
}
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 6254e9fbd8..af3bfe2c03 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -20,6 +20,9 @@
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
#include "nvim/memory_defs.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/channel_defs.h"
+#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/pos_defs.h"
@@ -697,3 +700,109 @@ static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope,
set_option_value_for(name.data, opt_idx, optval, opt_flags, req_scope, to, err);
});
}
+
+/// @deprecated Use nvim_exec_lua() instead.
+///
+/// Calls many API methods atomically.
+///
+/// This has two main usages:
+/// 1. To perform several requests from an async context atomically, i.e.
+/// without interleaving redraws, RPC requests from other clients, or user
+/// interactions (however API methods may trigger autocommands or event
+/// processing which have such side effects, e.g. |:sleep| may wake timers).
+/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests.
+///
+/// @param channel_id
+/// @param calls an array of calls, where each call is described by an array
+/// with two elements: the request name, and an array of arguments.
+/// @param[out] err Validation error details (malformed `calls` parameter),
+/// if any. Errors from batched calls are given in the return value.
+///
+/// @return Array of two elements. The first is an array of return
+/// values. The second is NIL if all calls succeeded. If a call resulted in
+/// an error, it is a three-element array with the zero-based index of the call
+/// which resulted in an error, the error type and the error message. If an
+/// error occurred, the values from all preceding calls will still be returned.
+Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *err)
+ FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(12) FUNC_API_REMOTE_ONLY
+{
+ Array rv = arena_array(arena, 2);
+ Array results = arena_array(arena, calls.size);
+ Error nested_error = ERROR_INIT;
+
+ size_t i; // also used for freeing the variables
+ for (i = 0; i < calls.size; i++) {
+ VALIDATE_T("'calls' item", kObjectTypeArray, calls.items[i].type, {
+ goto theend;
+ });
+ Array call = calls.items[i].data.array;
+ VALIDATE_EXP((call.size == 2), "'calls' item", "2-item Array", NULL, {
+ goto theend;
+ });
+ VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
+ goto theend;
+ });
+ String name = call.items[0].data.string;
+ VALIDATE_T("call args", kObjectTypeArray, call.items[1].type, {
+ goto theend;
+ });
+ Array args = call.items[1].data.array;
+
+ MsgpackRpcRequestHandler handler =
+ msgpack_rpc_get_handler_for(name.data,
+ name.size,
+ &nested_error);
+
+ if (ERROR_SET(&nested_error)) {
+ break;
+ }
+
+ Object result = handler.fn(channel_id, args, arena, &nested_error);
+ if (ERROR_SET(&nested_error)) {
+ // error handled after loop
+ break;
+ }
+ // TODO(bfredl): wasteful copy. It could be avoided to encoding to msgpack
+ // directly here. But `result` might become invalid when next api function
+ // is called in the loop.
+ ADD_C(results, copy_object(result, arena));
+ if (handler.ret_alloc) {
+ api_free_object(result);
+ }
+ }
+
+ ADD_C(rv, ARRAY_OBJ(results));
+ if (ERROR_SET(&nested_error)) {
+ Array errval = arena_array(arena, 3);
+ ADD_C(errval, INTEGER_OBJ((Integer)i));
+ ADD_C(errval, INTEGER_OBJ(nested_error.type));
+ ADD_C(errval, STRING_OBJ(copy_string(cstr_as_string(nested_error.msg), arena)));
+ ADD_C(rv, ARRAY_OBJ(errval));
+ } else {
+ ADD_C(rv, NIL);
+ }
+
+theend:
+ api_clear_error(&nested_error);
+ return rv;
+}
+
+/// @deprecated
+///
+/// @param channel_id Channel id (passed automatically by the dispatcher)
+/// @param event Event type string
+void nvim_subscribe(uint64_t channel_id, String event)
+ FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
+{
+ // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions".
+}
+
+/// @deprecated
+///
+/// @param channel_id Channel id (passed automatically by the dispatcher)
+/// @param event Event type string
+void nvim_unsubscribe(uint64_t channel_id, String event)
+ FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
+{
+ // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions".
+}
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 1b03a97edb..85cce45560 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -121,7 +121,7 @@ Array virt_text_to_array(VirtText vt, bool hl_name, Arena *arena)
Array hl_array = arena_array(arena, i < j ? j - i + 1 : 0);
for (; i < j; i++) {
int hl_id = kv_A(vt, i).hl_id;
- if (hl_id > 0) {
+ if (hl_id >= 0) {
ADD_C(hl_array, hl_group_name(hl_id, hl_name));
}
}
@@ -131,11 +131,11 @@ Array virt_text_to_array(VirtText vt, bool hl_name, Arena *arena)
Array chunk = arena_array(arena, 2);
ADD_C(chunk, CSTR_AS_OBJ(text));
if (hl_array.size > 0) {
- if (hl_id > 0) {
+ if (hl_id >= 0) {
ADD_C(hl_array, hl_group_name(hl_id, hl_name));
}
ADD_C(chunk, ARRAY_OBJ(hl_array));
- } else if (hl_id > 0) {
+ } else if (hl_id >= 0) {
ADD_C(chunk, hl_group_name(hl_id, hl_name));
}
ADD_C(chunks, ARRAY_OBJ(chunk));
@@ -489,8 +489,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// used together with virt_text.
/// - url: A URL to associate with this extmark. In the TUI, the OSC 8 control
/// sequence is used to generate a clickable hyperlink to this URL.
-/// - scoped: boolean that indicates that the extmark should only be
-/// displayed in the namespace scope. (experimental)
+/// - scoped: boolean (EXPERIMENTAL) enables "scoping" for the extmark. See
+/// |nvim__win_add_ns()|
///
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
@@ -682,7 +682,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
goto error;
});
- size_t len = 0;
+ colnr_T len = 0;
if (HAS_KEY(opts, set_extmark, spell)) {
hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff;
@@ -712,16 +712,16 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
});
line = buf->b_ml.ml_line_count;
} else if (line < buf->b_ml.ml_line_count) {
- len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1));
+ len = opts->ephemeral ? MAXCOL : ml_get_buf_len(buf, (linenr_T)line + 1);
}
if (col == -1) {
- col = (Integer)len;
- } else if (col > (Integer)len) {
+ col = len;
+ } else if (col > len) {
VALIDATE_RANGE(!strict, "col", {
goto error;
});
- col = (Integer)len;
+ col = len;
} else if (col < -1) {
VALIDATE_RANGE(false, "col", {
goto error;
@@ -730,7 +730,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (col2 >= 0) {
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
- len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1));
+ len = opts->ephemeral ? MAXCOL : ml_get_buf_len(buf, (linenr_T)line2 + 1);
} else if (line2 == buf->b_ml.ml_line_count) {
// We are trying to add an extmark past final newline
len = 0;
@@ -738,11 +738,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
// reuse len from before
line2 = (int)line;
}
- if (col2 > (Integer)len) {
+ if (col2 > len) {
VALIDATE_RANGE(!strict, "end_col", {
goto error;
});
- col2 = (int)len;
+ col2 = len;
}
} else if (line2 >= 0) {
col2 = 0;
@@ -761,32 +761,20 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
col2 = c;
}
- DecorPriority subpriority = DECOR_PRIORITY_BASE;
- if (HAS_KEY(opts, set_extmark, _subpriority)) {
- VALIDATE_RANGE((opts->_subpriority >= 0 && opts->_subpriority <= UINT16_MAX),
- "_subpriority", {
- goto error;
- });
- subpriority = (DecorPriority)opts->_subpriority;
- }
-
if (kv_size(virt_text.data.virt_text)) {
- decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true,
- subpriority);
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true);
}
if (kv_size(virt_lines.data.virt_lines)) {
- decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true,
- subpriority);
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true);
}
if (url != NULL) {
DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT;
sh.url = url;
- decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, 0, 0, subpriority);
+ decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, 0, 0);
}
if (has_hl) {
DecorSignHighlight sh = decor_sh_from_inline(hl);
- decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id,
- subpriority);
+ decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
}
} else {
if (opts->ephemeral) {
@@ -1177,7 +1165,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
String str = chunk.items[0].data.string;
- int hl_id = 0;
+ int hl_id = -1;
if (chunk.size == 2) {
Object hl = chunk.items[1];
if (hl.type == kObjectTypeArray) {
@@ -1227,13 +1215,15 @@ String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error
return mt_inspect(buf->b_marktree, keys, dot);
}
-/// Adds the namespace scope to the window.
+/// EXPERIMENTAL: this API will change in the future.
+///
+/// Scopes a namespace to the a window, so extmarks in the namespace will be active only in the
+/// given window.
///
/// @param window Window handle, or 0 for current window
-/// @param ns_id the namespace to add
+/// @param ns_id Namespace
/// @return true if the namespace was added, else false
-Boolean nvim_win_add_ns(Window window, Integer ns_id, Error *err)
- FUNC_API_SINCE(12)
+Boolean nvim__win_add_ns(Window window, Integer ns_id, Error *err)
{
win_T *win = find_window_by_handle(window, err);
if (!win) {
@@ -1246,17 +1236,20 @@ Boolean nvim_win_add_ns(Window window, Integer ns_id, Error *err)
set_put(uint32_t, &win->w_ns_set, (uint32_t)ns_id);
- changed_window_setting_win(win);
+ if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(win);
+ }
return true;
}
-/// Gets all the namespaces scopes associated with a window.
+/// EXPERIMENTAL: this API will change in the future.
+///
+/// Gets the namespace scopes for a given window.
///
/// @param window Window handle, or 0 for current window
/// @return a list of namespaces ids
-ArrayOf(Integer) nvim_win_get_ns(Window window, Arena *arena, Error *err)
- FUNC_API_SINCE(12)
+ArrayOf(Integer) nvim__win_get_ns(Window window, Arena *arena, Error *err)
{
win_T *win = find_window_by_handle(window, err);
if (!win) {
@@ -1272,13 +1265,14 @@ ArrayOf(Integer) nvim_win_get_ns(Window window, Arena *arena, Error *err)
return rv;
}
-/// Removes the namespace scope from the window.
+/// EXPERIMENTAL: this API will change in the future.
+///
+/// Unscopes a namespace (un-binds it from the given scope).
///
/// @param window Window handle, or 0 for current window
/// @param ns_id the namespace to remove
/// @return true if the namespace was removed, else false
-Boolean nvim_win_remove_ns(Window window, Integer ns_id, Error *err)
- FUNC_API_SINCE(12)
+Boolean nvim__win_del_ns(Window window, Integer ns_id, Error *err)
{
win_T *win = find_window_by_handle(window, err);
if (!win) {
@@ -1291,7 +1285,9 @@ Boolean nvim_win_remove_ns(Window window, Integer ns_id, Error *err)
set_del(uint32_t, &win->w_ns_set, (uint32_t)ns_id);
- changed_window_setting_win(win);
+ if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(win);
+ }
return true;
}
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
index fe91d9760d..00d8aa8428 100644
--- a/src/nvim/api/keysets_defs.h
+++ b/src/nvim/api/keysets_defs.h
@@ -56,8 +56,6 @@ typedef struct {
Boolean undo_restore;
String url;
Boolean scoped;
-
- Integer _subpriority;
} Dict(set_extmark);
typedef struct {
@@ -375,3 +373,17 @@ typedef struct {
Boolean ignore_blank_lines;
Boolean indent_heuristic;
} Dict(xdl_diff);
+
+typedef struct {
+ OptionalKeys is_set__redraw_;
+ Boolean flush;
+ Boolean cursor;
+ Boolean valid;
+ Boolean statuscolumn;
+ Boolean statusline;
+ Boolean tabline;
+ Boolean winbar;
+ Array range;
+ Window win;
+ Buffer buf;
+} Dict(redraw);
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index a70ef1e50b..a78d78c057 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -76,8 +76,7 @@ static Object typval_cbuf_to_obj(EncodedData *edata, const char *data, size_t le
do { \
ufunc_T *fp = find_func(fun); \
if (fp != NULL && (fp->uf_flags & FC_LUAREF)) { \
- LuaRef ref = api_new_luaref(fp->uf_luaref); \
- kvi_push(edata->stack, LUAREF_OBJ(ref)); \
+ kvi_push(edata->stack, LUAREF_OBJ(api_new_luaref(fp->uf_luaref))); \
} else { \
TYPVAL_ENCODE_CONV_NIL(tv); \
} \
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 1cd98aa0c4..a17e78cc31 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -524,10 +524,10 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
}
char *bufstr = ml_get_buf(buf, (linenr_T)lnum);
- size_t line_length = strlen(bufstr);
+ colnr_T line_length = ml_get_buf_len(buf, (linenr_T)lnum);
- start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
- end_col = end_col < 0 ? (int64_t)line_length + end_col + 1 : end_col;
+ start_col = start_col < 0 ? line_length + start_col + 1 : start_col;
+ end_col = end_col < 0 ? line_length + end_col + 1 : end_col;
if (start_col >= MAXCOL || end_col >= MAXCOL) {
api_set_error(err, kErrorTypeValidation, "Column index is too high");
@@ -539,7 +539,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
return rv;
}
- if ((size_t)start_col >= line_length) {
+ if (start_col >= line_length) {
return rv;
}
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 040abb1e3f..56a3f1cf23 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -146,7 +146,11 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err)
}
if (tp == curtab) {
- win_enter(wp, true);
+ try_start();
+ win_goto(wp);
+ if (!try_end(err) && curwin != wp) {
+ api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win);
+ }
} else if (tp->tp_curwin != wp) {
tp->tp_prevwin = tp->tp_curwin;
tp->tp_curwin = wp;
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 692e3f95fc..fdf25c75d7 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -67,7 +67,6 @@ static void mpack_str_small(char **buf, const char *str, size_t len)
static void remote_ui_destroy(RemoteUI *ui)
FUNC_ATTR_NONNULL_ALL
{
- kv_destroy(ui->call_buf);
xfree(ui->packer.startptr);
XFREE_CLEAR(ui->term_name);
xfree(ui);
@@ -190,8 +189,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
.anydata = ui,
};
ui->wildmenu_active = false;
- ui->call_buf = (Array)ARRAY_DICT_INIT;
- kv_ensure_space(ui->call_buf, 16);
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
ui_attach_impl(ui, channel_id);
@@ -533,7 +530,8 @@ static void ui_alloc_buf(RemoteUI *ui)
static void prepare_call(RemoteUI *ui, const char *name)
{
- if (ui->packer.startptr && BUF_POS(ui) > UI_BUF_SIZE - EVENT_BUF_SIZE) {
+ if (ui->packer.startptr
+ && (BUF_POS(ui) > UI_BUF_SIZE - EVENT_BUF_SIZE || ui->ncells_pending >= 500)) {
ui_flush_buf(ui);
}
@@ -582,7 +580,7 @@ static void ui_flush_callback(PackerBuffer *packer)
void remote_ui_grid_clear(RemoteUI *ui, Integer grid)
{
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 1);
if (ui->ui_ext[kUILinegrid]) {
ADD_C(args, INTEGER_OBJ(grid));
}
@@ -592,7 +590,7 @@ void remote_ui_grid_clear(RemoteUI *ui, Integer grid)
void remote_ui_grid_resize(RemoteUI *ui, Integer grid, Integer width, Integer height)
{
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 3);
if (ui->ui_ext[kUILinegrid]) {
ADD_C(args, INTEGER_OBJ(grid));
} else {
@@ -608,7 +606,7 @@ void remote_ui_grid_scroll(RemoteUI *ui, Integer grid, Integer top, Integer bot,
Integer right, Integer rows, Integer cols)
{
if (ui->ui_ext[kUILinegrid]) {
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 7);
ADD_C(args, INTEGER_OBJ(grid));
ADD_C(args, INTEGER_OBJ(top));
ADD_C(args, INTEGER_OBJ(bot));
@@ -618,20 +616,19 @@ void remote_ui_grid_scroll(RemoteUI *ui, Integer grid, Integer top, Integer bot,
ADD_C(args, INTEGER_OBJ(cols));
push_call(ui, "grid_scroll", args);
} else {
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 4);
ADD_C(args, INTEGER_OBJ(top));
ADD_C(args, INTEGER_OBJ(bot - 1));
ADD_C(args, INTEGER_OBJ(left));
ADD_C(args, INTEGER_OBJ(right - 1));
push_call(ui, "set_scroll_region", args);
- args = ui->call_buf;
+ kv_size(args) = 0;
ADD_C(args, INTEGER_OBJ(rows));
push_call(ui, "scroll", args);
- // some clients have "clear" being affected by scroll region,
- // so reset it.
- args = ui->call_buf;
+ // some clients have "clear" being affected by scroll region, so reset it.
+ kv_size(args) = 0;
ADD_C(args, INTEGER_OBJ(0));
ADD_C(args, INTEGER_OBJ(ui->height - 1));
ADD_C(args, INTEGER_OBJ(0));
@@ -646,7 +643,7 @@ void remote_ui_default_colors_set(RemoteUI *ui, Integer rgb_fg, Integer rgb_bg,
if (!ui->ui_ext[kUITermColors]) {
HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp);
}
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 5);
ADD_C(args, INTEGER_OBJ(rgb_fg));
ADD_C(args, INTEGER_OBJ(rgb_bg));
ADD_C(args, INTEGER_OBJ(rgb_sp));
@@ -656,15 +653,15 @@ void remote_ui_default_colors_set(RemoteUI *ui, Integer rgb_fg, Integer rgb_bg,
// Deprecated
if (!ui->ui_ext[kUILinegrid]) {
- args = ui->call_buf;
+ kv_size(args) = 0;
ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
push_call(ui, "update_fg", args);
- args = ui->call_buf;
+ kv_size(args) = 0;
ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1));
push_call(ui, "update_bg", args);
- args = ui->call_buf;
+ kv_size(args) = 0;
ADD_C(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1));
push_call(ui, "update_sp", args);
}
@@ -677,7 +674,7 @@ void remote_ui_hl_attr_define(RemoteUI *ui, Integer id, HlAttrs rgb_attrs, HlAtt
return;
}
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 4);
ADD_C(args, INTEGER_OBJ(id));
MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE);
MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
@@ -705,14 +702,14 @@ void remote_ui_hl_attr_define(RemoteUI *ui, Integer id, HlAttrs rgb_attrs, HlAtt
void remote_ui_highlight_set(RemoteUI *ui, int id)
{
- Array args = ui->call_buf;
-
if (ui->hl_id == id) {
return;
}
+
ui->hl_id = id;
MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
hlattrs2dict(&dict, NULL, syn_attr2entry(id), ui->rgb, false);
+ MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, DICTIONARY_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -721,7 +718,7 @@ void remote_ui_highlight_set(RemoteUI *ui, int id)
void remote_ui_grid_cursor_goto(RemoteUI *ui, Integer grid, Integer row, Integer col)
{
if (ui->ui_ext[kUILinegrid]) {
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ(grid));
ADD_C(args, INTEGER_OBJ(row));
ADD_C(args, INTEGER_OBJ(col));
@@ -741,7 +738,7 @@ void remote_ui_cursor_goto(RemoteUI *ui, Integer row, Integer col)
}
ui->client_row = row;
ui->client_col = col;
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, INTEGER_OBJ(row));
ADD_C(args, INTEGER_OBJ(col));
push_call(ui, "cursor_goto", args);
@@ -750,7 +747,7 @@ void remote_ui_cursor_goto(RemoteUI *ui, Integer row, Integer col)
void remote_ui_put(RemoteUI *ui, const char *cell)
{
ui->client_col++;
- Array args = ui->call_buf;
+ MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, CSTR_AS_OBJ(cell));
push_call(ui, "put", args);
}
@@ -781,11 +778,14 @@ void remote_ui_raw_line(RemoteUI *ui, Integer grid, Integer row, Integer startco
for (size_t i = 0; i < ncells; i++) {
repeat++;
if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
- if (UI_BUF_SIZE - BUF_POS(ui) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
+ if (UI_BUF_SIZE - BUF_POS(ui) < 2 * (1 + 2 + MAX_SCHAR_SIZE + 5 + 5) + 1
+ || ui->ncells_pending >= 500) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
// For simplicity leave place for the final "clear" element
// as well, hence the factor of 2 in the check.
+ // Also if there is a lot of packed cells, pass them of to the UI to
+ // let it start processing them
mpack_w2(&lenpos, nelem);
// We only ever set the wrap field on the final "grid_line" event for the line.
@@ -831,11 +831,6 @@ void remote_ui_raw_line(RemoteUI *ui, Integer grid, Integer row, Integer startco
}
mpack_w2(&lenpos, nelem);
mpack_bool(buf, flags & kLineFlagWrap);
-
- if (ui->ncells_pending > 500) {
- // pass off cells to UI to let it start processing them
- ui_flush_buf(ui);
- }
} else {
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
@@ -951,12 +946,12 @@ void remote_ui_event(RemoteUI *ui, char *name, Array args)
push_call(ui, name, new_args);
goto free_ret;
} else if (strequal(name, "cmdline_block_show")) {
- Array new_args = ui->call_buf;
Array block = args.items[0].data.array;
Array new_block = arena_array(&arena, block.size);
for (size_t i = 0; i < block.size; i++) {
ADD_C(new_block, ARRAY_OBJ(translate_contents(ui, block.items[i].data.array, &arena)));
}
+ MAXSIZE_TEMP_ARRAY(new_args, 1);
ADD_C(new_args, ARRAY_OBJ(new_block));
push_call(ui, name, new_args);
goto free_ret;
@@ -973,18 +968,18 @@ void remote_ui_event(RemoteUI *ui, char *name, Array args)
ui->wildmenu_active = (args.items[4].data.integer == -1)
|| !ui->ui_ext[kUIPopupmenu];
if (ui->wildmenu_active) {
- Array new_args = ui->call_buf;
Array items = args.items[0].data.array;
Array new_items = arena_array(&arena, items.size);
for (size_t i = 0; i < items.size; i++) {
ADD_C(new_items, items.items[i].data.array.items[0]);
}
+ MAXSIZE_TEMP_ARRAY(new_args, 1);
ADD_C(new_args, ARRAY_OBJ(new_items));
push_call(ui, "wildmenu_show", new_args);
if (args.items[1].data.integer != -1) {
- Array new_args2 = ui->call_buf;
- ADD_C(new_args2, args.items[1]);
- push_call(ui, "wildmenu_select", new_args2);
+ kv_size(new_args) = 0;
+ ADD_C(new_args, args.items[1]);
+ push_call(ui, "wildmenu_select", new_args);
}
goto free_ret;
}
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index c2f02c34f8..2bd8792d71 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -118,6 +118,10 @@ void win_viewport(Integer grid, Window win, Integer topline, Integer botline, In
Integer curcol, Integer line_count, Integer scroll_delta)
FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE;
+void win_viewport_margins(Integer grid, Window win, Integer top, Integer bottom, Integer left,
+ Integer right)
+ FUNC_API_SINCE(12) FUNC_API_CLIENT_IGNORE;
+
void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col)
FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 84a2f24dbc..fc780e1248 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -45,10 +45,12 @@
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
+#include "nvim/lua/treesitter.h"
#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mark_defs.h"
+#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -167,7 +169,7 @@ Dictionary nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, E
/// @param[out] err Error details, if any
///
// TODO(bfredl): val should take update vs reset flag
-void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
+void nvim_set_hl(uint64_t channel_id, Integer ns_id, String name, Dict(highlight) *val, Error *err)
FUNC_API_SINCE(7)
{
int hl_id = syn_check_group(name.data, name.size);
@@ -184,7 +186,9 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
if (!ERROR_SET(err)) {
- ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val);
+ });
}
}
@@ -275,6 +279,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
bool typed = false;
bool execute = false;
bool dangerous = false;
+ bool lowlevel = false;
for (size_t i = 0; i < mode.size; i++) {
switch (mode.data[i]) {
@@ -290,6 +295,8 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
execute = true; break;
case '!':
dangerous = true; break;
+ case 'L':
+ lowlevel = true; break;
}
}
@@ -305,10 +312,14 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
} else {
keys_esc = keys.data;
}
- ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
- insert ? 0 : typebuf.tb_len, !typed, false);
- if (vgetc_busy) {
- typebuf_was_filled = true;
+ if (lowlevel) {
+ input_enqueue_raw(cstr_as_string(keys_esc));
+ } else {
+ ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
+ insert ? 0 : typebuf.tb_len, !typed, false);
+ if (vgetc_busy) {
+ typebuf_was_filled = true;
+ }
}
if (escape_ks) {
@@ -876,6 +887,11 @@ void nvim_set_current_buf(Buffer buffer, Error *err)
return;
}
+ if (curwin->w_p_wfb) {
+ api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
+ return;
+ }
+
try_start();
int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
if (!try_end(err) && result == FAIL) {
@@ -953,21 +969,21 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
FUNC_API_SINCE(6)
{
try_start();
+ // Block autocommands for now so they don't mess with the buffer before we
+ // finish configuring it.
+ block_autocmds();
+
buf_T *buf = buflist_new(NULL, NULL, 0,
BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
- try_end(err);
if (buf == NULL) {
+ unblock_autocmds();
goto fail;
}
// Open the memline for the buffer. This will avoid spurious autocmds when
// a later nvim_buf_set_lines call would have needed to "open" the buffer.
- try_start();
- block_autocmds();
- int status = ml_open(buf);
- unblock_autocmds();
- try_end(err);
- if (status == FAIL) {
+ if (ml_open(buf) == FAIL) {
+ unblock_autocmds();
goto fail;
}
@@ -978,21 +994,39 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
buf->b_last_changedtick_pum = buf_get_changedtick(buf);
// Only strictly needed for scratch, but could just as well be consistent
- // and do this now. buffer is created NOW, not when it latter first happen
+ // and do this now. Buffer is created NOW, not when it later first happens
// to reach a window or aucmd_prepbuf() ..
buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
if (scratch) {
- set_string_option_direct_in_buf(buf, kOptBufhidden, "hide", OPT_LOCAL, 0);
- set_string_option_direct_in_buf(buf, kOptBuftype, "nofile", OPT_LOCAL, 0);
+ set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, kOptReqBuf,
+ buf);
+ set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, kOptReqBuf,
+ buf);
assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
buf->b_p_swf = false;
buf->b_p_ml = false;
}
+
+ unblock_autocmds();
+
+ bufref_T bufref;
+ set_bufref(&bufref, buf);
+ if (apply_autocmds(EVENT_BUFNEW, NULL, NULL, false, buf)
+ && !bufref_valid(&bufref)) {
+ goto fail;
+ }
+ if (listed
+ && apply_autocmds(EVENT_BUFADD, NULL, NULL, false, buf)
+ && !bufref_valid(&bufref)) {
+ goto fail;
+ }
+
+ try_end(err);
return buf->b_fnum;
fail:
- if (!ERROR_SET(err)) {
+ if (!try_end(err)) {
api_set_error(err, kErrorTypeException, "Failed to create buffer");
}
return 0;
@@ -1300,36 +1334,6 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
});
}
-/// Subscribes to event broadcasts.
-///
-/// @param channel_id Channel id (passed automatically by the dispatcher)
-/// @param event Event type string
-void nvim_subscribe(uint64_t channel_id, String event)
- FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
-{
- size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN);
- char e[METHOD_MAXLEN + 1];
- memcpy(e, event.data, length);
- e[length] = NUL;
- rpc_subscribe(channel_id, e);
-}
-
-/// Unsubscribes to event broadcasts.
-///
-/// @param channel_id Channel id (passed automatically by the dispatcher)
-/// @param event Event type string
-void nvim_unsubscribe(uint64_t channel_id, String event)
- FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
-{
- size_t length = (event.size < METHOD_MAXLEN
- ? event.size
- : METHOD_MAXLEN);
- char e[METHOD_MAXLEN + 1];
- memcpy(e, event.data, length);
- e[length] = NUL;
- rpc_unsubscribe(channel_id, e);
-}
-
/// Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or
/// "#rrggbb" hexadecimal string.
///
@@ -1666,90 +1670,6 @@ Array nvim_list_chans(Arena *arena)
return channel_all_info(arena);
}
-/// Calls many API methods atomically.
-///
-/// This has two main usages:
-/// 1. To perform several requests from an async context atomically, i.e.
-/// without interleaving redraws, RPC requests from other clients, or user
-/// interactions (however API methods may trigger autocommands or event
-/// processing which have such side effects, e.g. |:sleep| may wake timers).
-/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests.
-///
-/// @param channel_id
-/// @param calls an array of calls, where each call is described by an array
-/// with two elements: the request name, and an array of arguments.
-/// @param[out] err Validation error details (malformed `calls` parameter),
-/// if any. Errors from batched calls are given in the return value.
-///
-/// @return Array of two elements. The first is an array of return
-/// values. The second is NIL if all calls succeeded. If a call resulted in
-/// an error, it is a three-element array with the zero-based index of the call
-/// which resulted in an error, the error type and the error message. If an
-/// error occurred, the values from all preceding calls will still be returned.
-Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *err)
- FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
-{
- Array rv = arena_array(arena, 2);
- Array results = arena_array(arena, calls.size);
- Error nested_error = ERROR_INIT;
-
- size_t i; // also used for freeing the variables
- for (i = 0; i < calls.size; i++) {
- VALIDATE_T("'calls' item", kObjectTypeArray, calls.items[i].type, {
- goto theend;
- });
- Array call = calls.items[i].data.array;
- VALIDATE_EXP((call.size == 2), "'calls' item", "2-item Array", NULL, {
- goto theend;
- });
- VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
- goto theend;
- });
- String name = call.items[0].data.string;
- VALIDATE_T("call args", kObjectTypeArray, call.items[1].type, {
- goto theend;
- });
- Array args = call.items[1].data.array;
-
- MsgpackRpcRequestHandler handler =
- msgpack_rpc_get_handler_for(name.data,
- name.size,
- &nested_error);
-
- if (ERROR_SET(&nested_error)) {
- break;
- }
-
- Object result = handler.fn(channel_id, args, arena, &nested_error);
- if (ERROR_SET(&nested_error)) {
- // error handled after loop
- break;
- }
- // TODO(bfredl): wasteful copy. It could be avoided to encoding to msgpack
- // directly here. But `result` might become invalid when next api function
- // is called in the loop.
- ADD_C(results, copy_object(result, arena));
- if (handler.ret_alloc) {
- api_free_object(result);
- }
- }
-
- ADD_C(rv, ARRAY_OBJ(results));
- if (ERROR_SET(&nested_error)) {
- Array errval = arena_array(arena, 3);
- ADD_C(errval, INTEGER_OBJ((Integer)i));
- ADD_C(errval, INTEGER_OBJ(nested_error.type));
- ADD_C(errval, STRING_OBJ(copy_string(cstr_as_string(nested_error.msg), arena)));
- ADD_C(rv, ARRAY_OBJ(errval));
- } else {
- ADD_C(rv, NIL);
- }
-
-theend:
- api_clear_error(&nested_error);
- return rv;
-}
-
/// Writes a message to vim output or error buffer. The string is split
/// and flushed after each newline. Incomplete lines are kept for writing
/// later.
@@ -1858,12 +1778,13 @@ Float nvim__id_float(Float flt)
/// @return Map of various internal stats.
Dictionary nvim__stats(Arena *arena)
{
- Dictionary rv = arena_dict(arena, 5);
+ Dictionary rv = arena_dict(arena, 6);
PUT_C(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT_C(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip));
PUT_C(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
PUT_C(rv, "redraw", INTEGER_OBJ(g_stats.redraw));
PUT_C(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count));
+ PUT_C(rv, "ts_query_parse_count", INTEGER_OBJ((Integer)tslua_query_parse_count));
return rv;
}
@@ -2332,20 +2253,18 @@ void nvim_error_event(uint64_t channel_id, Integer lvl, String data)
ELOG("async error on channel %" PRId64 ": %s", channel_id, data.size ? data.data : "");
}
-/// Set info for the completion candidate index.
-/// if the info was shown in a window, then the
-/// window and buffer ids are returned for further
-/// customization. If the text was not shown, an
-/// empty dict is returned.
+/// EXPERIMENTAL: this API may change in the future.
///
-/// @param index the completion candidate index
+/// Sets info for the completion item at the given index. If the info text was shown in a window,
+/// returns the window and buffer ids, or empty dict if not shown.
+///
+/// @param index Completion candidate index
/// @param opts Optional parameters.
/// - info: (string) info text.
/// @return Dictionary containing these keys:
/// - winid: (number) floating window id
/// - bufnr: (number) buffer id in floating window
-Dictionary nvim_complete_set(Integer index, Dict(complete_set) *opts, Arena *arena)
- FUNC_API_SINCE(12)
+Dictionary nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena)
{
Dictionary rv = arena_dict(arena, 2);
if (HAS_KEY(opts, complete_set, info)) {
@@ -2357,3 +2276,159 @@ Dictionary nvim_complete_set(Integer index, Dict(complete_set) *opts, Arena *are
}
return rv;
}
+
+static void redraw_status(win_T *wp, Dict(redraw) *opts, bool *flush)
+{
+ if (opts->statuscolumn && *wp->w_p_stc != NUL) {
+ wp->w_nrwidth_line_count = 0;
+ changed_window_setting(wp);
+ }
+ win_grid_alloc(wp);
+
+ // Flush later in case winbar was just hidden or shown for the first time, or
+ // statuscolumn is being drawn.
+ if (wp->w_lines_valid == 0) {
+ *flush = true;
+ }
+
+ // Mark for redraw in case flush will happen, otherwise redraw now.
+ if (*flush && (opts->statusline || opts->winbar)) {
+ wp->w_redr_status = true;
+ } else if (opts->statusline || opts->winbar) {
+ win_check_ns_hl(wp);
+ if (opts->winbar) {
+ win_redr_winbar(wp);
+ }
+ if (opts->statusline) {
+ win_redr_status(wp);
+ }
+ win_check_ns_hl(NULL);
+ }
+}
+
+/// EXPERIMENTAL: this API may change in the future.
+///
+/// Instruct Nvim to redraw various components.
+///
+/// @see |:redraw|
+///
+/// @param opts Optional parameters.
+/// - win: Target a specific |window-ID| as described below.
+/// - buf: Target a specific buffer number as described below.
+/// - flush: Update the screen with pending updates.
+/// - valid: When present mark `win`, `buf`, or all windows for
+/// redraw. When `true`, only redraw changed lines (useful for
+/// decoration providers). When `false`, forcefully redraw.
+/// - range: Redraw a range in `buf`, the buffer in `win` or the
+/// current buffer (useful for decoration providers). Expects a
+/// tuple `[first, last]` with the first and last line number
+/// of the range, 0-based end-exclusive |api-indexing|.
+/// - cursor: Immediately update cursor position on the screen in
+/// `win` or the current window.
+/// - statuscolumn: Redraw the 'statuscolumn' in `buf`, `win` or
+/// all windows.
+/// - statusline: Redraw the 'statusline' in `buf`, `win` or all
+/// windows.
+/// - winbar: Redraw the 'winbar' in `buf`, `win` or all windows.
+/// - tabline: Redraw the 'tabline'.
+void nvim__redraw(Dict(redraw) *opts, Error *err)
+ FUNC_API_SINCE(12)
+{
+ win_T *win = NULL;
+ buf_T *buf = NULL;
+
+ if (HAS_KEY(opts, redraw, win)) {
+ win = find_window_by_handle(opts->win, err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+ }
+
+ if (HAS_KEY(opts, redraw, buf)) {
+ VALIDATE(win == NULL, "%s", "cannot use both 'buf' and 'win'", {
+ return;
+ });
+ buf = find_buffer_by_handle(opts->buf, err);
+ if (ERROR_SET(err)) {
+ return;
+ }
+ }
+
+ int count = (win != NULL) + (buf != NULL);
+ VALIDATE(popcount(opts->is_set__redraw_) > count, "%s", "at least one action required", {
+ return;
+ });
+
+ if (HAS_KEY(opts, redraw, valid)) {
+ // UPD_VALID redraw type does not actually do anything on it's own. Setting
+ // it here without scrolling or changing buffer text seems pointless but
+ // the expectation is that this may be called by decoration providers whose
+ // "on_win" callback may set "w_redr_top/bot".
+ int type = opts->valid ? UPD_VALID : UPD_NOT_VALID;
+ if (win != NULL) {
+ redraw_later(win, type);
+ } else if (buf != NULL) {
+ redraw_buf_later(buf, type);
+ } else {
+ redraw_all_later(type);
+ }
+ }
+
+ if (HAS_KEY(opts, redraw, range)) {
+ VALIDATE(kv_size(opts->range) == 2
+ && kv_A(opts->range, 0).type == kObjectTypeInteger
+ && kv_A(opts->range, 1).type == kObjectTypeInteger
+ && kv_A(opts->range, 0).data.integer >= 0
+ && kv_A(opts->range, 1).data.integer >= -1,
+ "%s", "Invalid 'range': Expected 2-tuple of Integers", {
+ return;
+ });
+ linenr_T first = (linenr_T)kv_A(opts->range, 0).data.integer + 1;
+ linenr_T last = (linenr_T)kv_A(opts->range, 1).data.integer;
+ buf_T *rbuf = win ? win->w_buffer : (buf ? buf : curbuf);
+ if (last == -1) {
+ last = rbuf->b_ml.ml_line_count;
+ }
+ redraw_buf_range_later(rbuf, first, last);
+ }
+
+ if (opts->cursor) {
+ setcursor_mayforce(win ? win : curwin, true);
+ }
+
+ bool flush = opts->flush;
+ if (opts->tabline) {
+ // Flush later in case tabline was just hidden or shown for the first time.
+ if (redraw_tabline && firstwin->w_lines_valid == 0) {
+ flush = true;
+ } else {
+ draw_tabline();
+ }
+ }
+
+ bool save_lz = p_lz;
+ int save_rd = RedrawingDisabled;
+ RedrawingDisabled = 0;
+ p_lz = false;
+ if (opts->statuscolumn || opts->statusline || opts->winbar) {
+ if (win == NULL) {
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (buf == NULL || wp->w_buffer == buf) {
+ redraw_status(wp, opts, &flush);
+ }
+ }
+ } else {
+ redraw_status(win, opts, &flush);
+ }
+ }
+
+ // Flush pending screen updates if "flush" or "clear" is true, or when
+ // redrawing a status component may have changed the grid dimensions.
+ if (flush && !cmdpreview) {
+ update_screen();
+ }
+ ui_flush();
+
+ RedrawingDisabled = save_rd;
+ p_lz = save_lz;
+}
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 543c7b8113..3a9986a7d1 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -12,6 +12,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
+#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/decoration_defs.h"
@@ -198,9 +199,8 @@
/// - footer_pos: Footer position. Must be set with `footer` option.
/// Value can be one of "left", "center", or "right".
/// Default is `"left"`.
-/// - noautocmd: If true then no buffer-related autocommand events such as
-/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
-/// calling this function.
+/// - noautocmd: If true then all autocommands are blocked for the duration of
+/// the call.
/// - fixed: If true when anchor is NW or SW, the float window
/// would be kept fixed even if the window would be truncated.
/// - hide: If true the floating window will be hidden.
@@ -224,25 +224,33 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
}
WinConfig fconfig = WIN_CONFIG_INIT;
- if (!parse_float_config(config, &fconfig, false, true, err)) {
+ if (!parse_win_config(NULL, config, &fconfig, false, err)) {
return 0;
}
bool is_split = HAS_KEY_X(config, split) || HAS_KEY_X(config, vertical);
+ Window rv = 0;
+ if (fconfig.noautocmd) {
+ block_autocmds();
+ }
win_T *wp = NULL;
tabpage_T *tp = curtab;
+ win_T *parent = NULL;
+ if (config->win != -1) {
+ parent = find_window_by_handle(fconfig.window, err);
+ if (!parent) {
+ // find_window_by_handle has already set the error
+ goto cleanup;
+ } else if (is_split && parent->w_floating) {
+ api_set_error(err, kErrorTypeException, "Cannot split a floating window");
+ goto cleanup;
+ }
+ tp = win_find_tabpage(parent);
+ }
if (is_split) {
- win_T *parent = NULL;
- if (config->win != -1) {
- parent = find_window_by_handle(fconfig.window, err);
- if (!parent) {
- // find_window_by_handle has already set the error
- return 0;
- } else if (parent->w_floating) {
- api_set_error(err, kErrorTypeException, "Cannot split a floating window");
- return 0;
- }
+ if (!check_split_disallowed_err(parent ? parent : curwin, err)) {
+ goto cleanup; // error already set
}
if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) {
@@ -254,18 +262,20 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
}
int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER;
- if (parent == NULL) {
- wp = win_split_ins(0, flags, NULL, 0);
- } else {
- tp = win_find_tabpage(parent);
- switchwin_T switchwin;
- // `parent` is valid in `tp`, so switch_win should not fail.
- const int result = switch_win(&switchwin, parent, tp, true);
- (void)result;
- assert(result == OK);
- wp = win_split_ins(0, flags, NULL, 0);
- restore_win(&switchwin, true);
- }
+ TRY_WRAP(err, {
+ int size = (flags & WSP_VERT) ? fconfig.width : fconfig.height;
+ if (parent == NULL || parent == curwin) {
+ wp = win_split_ins(size, flags, NULL, 0, NULL);
+ } else {
+ switchwin_T switchwin;
+ // `parent` is valid in `tp`, so switch_win should not fail.
+ const int result = switch_win(&switchwin, parent, tp, true);
+ assert(result == OK);
+ (void)result;
+ wp = win_split_ins(size, flags, NULL, 0, NULL);
+ restore_win(&switchwin, true);
+ }
+ });
if (wp) {
wp->w_config = fconfig;
}
@@ -273,30 +283,64 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err
wp = win_new_float(NULL, false, fconfig, err);
}
if (!wp) {
- api_set_error(err, kErrorTypeException, "Failed to create window");
- return 0;
+ if (!ERROR_SET(err)) {
+ api_set_error(err, kErrorTypeException, "Failed to create window");
+ }
+ goto cleanup;
}
- switchwin_T switchwin;
- if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
- apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
+
+ // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each
+ // event. In each case, `wp` should already be valid in `tp`, so switch_win should not fail.
+ // Also, autocommands may free the `buf` to switch to, so store a bufref to check.
+ bufref_T bufref;
+ set_bufref(&bufref, buf);
+ if (!fconfig.noautocmd) {
+ switchwin_T switchwin;
+ const int result = switch_win_noblock(&switchwin, wp, tp, true);
+ assert(result == OK);
+ (void)result;
+ if (apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf)) {
+ tp = win_find_tabpage(wp);
+ }
+ restore_win_noblock(&switchwin, true);
}
- restore_win_noblock(&switchwin, true);
- if (enter) {
+ if (tp && enter) {
goto_tabpage_win(tp, wp);
+ tp = win_find_tabpage(wp);
}
- if (win_valid_any_tab(wp) && buf != wp->w_buffer) {
- win_set_buf(wp, buf, !enter || fconfig.noautocmd, err);
+ if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) {
+ // win_set_buf temporarily makes `wp` the curwin to set the buffer.
+ // If not entering `wp`, block Enter and Leave events. (cringe)
+ const bool au_no_enter_leave = curwin != wp && !fconfig.noautocmd;
+ if (au_no_enter_leave) {
+ autocmd_no_enter++;
+ autocmd_no_leave++;
+ }
+ win_set_buf(wp, buf, err);
+ if (!fconfig.noautocmd) {
+ tp = win_find_tabpage(wp);
+ }
+ if (au_no_enter_leave) {
+ autocmd_no_enter--;
+ autocmd_no_leave--;
+ }
}
- if (!win_valid_any_tab(wp)) {
+ if (!tp) {
api_set_error(err, kErrorTypeException, "Window was closed immediately");
- return 0;
+ goto cleanup;
}
if (fconfig.style == kWinStyleMinimal) {
win_set_minimal_style(wp);
didset_window_options(wp, true);
}
- return wp->handle;
+ rv = wp->handle;
+
+cleanup:
+ if (fconfig.noautocmd) {
+ unblock_autocmds();
+ }
+ return rv;
#undef HAS_KEY_X
}
@@ -330,11 +374,11 @@ static int win_split_flags(WinSplit split, bool toplevel)
return flags;
}
-/// Configures window layout. Currently only for floating and external windows
-/// (including changing a split window to those layouts).
+/// Configures window layout. Cannot be used to move the last window in a
+/// tabpage to a different one.
///
-/// When reconfiguring a floating window, absent option keys will not be
-/// changed. `row`/`col` and `relative` must be reconfigured together.
+/// When reconfiguring a window, absent option keys will not be changed.
+/// `row`/`col` and `relative` must be reconfigured together.
///
/// @see |nvim_open_win()|
///
@@ -350,6 +394,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
if (!win) {
return;
}
+
tabpage_T *win_tp = win_find_tabpage(win);
bool was_split = !win->w_floating;
bool has_split = HAS_KEY_X(config, split);
@@ -361,26 +406,31 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
&& !(HAS_KEY_X(config, external) ? config->external : fconfig.external)
&& (has_split || has_vertical || was_split);
- if (!parse_float_config(config, &fconfig, !was_split || to_split, false, err)) {
+ if (!parse_win_config(win, config, &fconfig, !was_split || to_split, err)) {
return;
}
+ win_T *parent = NULL;
+ if (config->win != -1) {
+ parent = find_window_by_handle(fconfig.window, err);
+ if (!parent) {
+ return;
+ } else if (to_split && parent->w_floating) {
+ api_set_error(err, kErrorTypeException, "Cannot split a floating window");
+ return;
+ }
+
+ // Prevent autocmd window from being moved into another tabpage
+ if (is_aucmd_win(win) && win_find_tabpage(win) != win_find_tabpage(parent)) {
+ api_set_error(err, kErrorTypeException, "Cannot move autocmd win to another tabpage");
+ return;
+ }
+ }
if (was_split && !to_split) {
if (!win_new_float(win, false, fconfig, err)) {
return;
}
redraw_later(win, UPD_NOT_VALID);
} else if (to_split) {
- win_T *parent = NULL;
- if (config->win != -1) {
- parent = find_window_by_handle(fconfig.window, err);
- if (!parent) {
- return;
- } else if (parent->w_floating) {
- api_set_error(err, kErrorTypeException, "Cannot split a floating window");
- return;
- }
- }
-
WinSplit old_split = win_split_dir(win);
if (has_vertical && !has_split) {
if (config->vertical) {
@@ -413,17 +463,59 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
return;
}
- if (was_split) {
- win_T *new_curwin = NULL;
+ if (!check_split_disallowed_err(win, err)) {
+ return; // error already set
+ }
+ // Can't move the cmdwin or its old curwin to a different tabpage.
+ if ((win == cmdwin_win || win == cmdwin_old_curwin) && parent != NULL
+ && win_find_tabpage(parent) != win_tp) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return;
+ }
+
+ bool to_split_ok = false;
+ // If we are moving curwin to another tabpage, switch windows *before* we remove it from the
+ // window list or remove its frame (if non-floating), so it's valid for autocommands.
+ const bool curwin_moving_tp
+ = win == curwin && parent != NULL && win_tp != win_find_tabpage(parent);
+ if (curwin_moving_tp) {
+ if (was_split) {
+ int dir;
+ win_goto(winframe_find_altwin(win, &dir, NULL, NULL));
+ } else {
+ win_goto(win_float_find_altwin(win, NULL));
+ }
+
+ // Autocommands may have been a real nuisance and messed things up...
+ if (curwin == win) {
+ api_set_error(err, kErrorTypeException, "Failed to switch away from window %d",
+ win->handle);
+ return;
+ }
+ win_tp = win_find_tabpage(win);
+ if (!win_tp || !win_valid_any_tab(parent)) {
+ api_set_error(err, kErrorTypeException, "Windows to split were closed");
+ goto restore_curwin;
+ }
+ if (was_split == win->w_floating || parent->w_floating) {
+ api_set_error(err, kErrorTypeException, "Floating state of windows to split changed");
+ goto restore_curwin;
+ }
+ }
+ int dir = 0;
+ frame_T *unflat_altfr = NULL;
+ win_T *altwin = NULL;
+
+ if (was_split) {
// If the window is the last in the tabpage or `fconfig.win` is
// a handle to itself, we can't split it.
if (win->w_frame->fr_parent == NULL) {
// FIXME(willothy): if the window is the last in the tabpage but there is another tabpage
// and the target window is in that other tabpage, should we move the window to that
// tabpage and close the previous one, or just error?
- api_set_error(err, kErrorTypeValidation, "Cannot move last window");
- return;
+ api_set_error(err, kErrorTypeException, "Cannot move last window");
+ goto restore_curwin;
} else if (parent != NULL && parent->handle == win->handle) {
int n_frames = 0;
for (frame_T *fr = win->w_frame->fr_parent->fr_child; fr != NULL; fr = fr->fr_next) {
@@ -459,83 +551,82 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
}
// If the frame doesn't have a parent, the old frame
// was the root frame and we need to create a top-level split.
- int dir;
- new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp);
+ altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr);
} else if (n_frames == 2) {
// There are two windows in the frame, we can just rotate it.
- int dir;
- neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp);
- new_curwin = neighbor;
+ altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr);
+ neighbor = altwin;
} else {
// There is only one window in the frame, we can't split it.
- api_set_error(err, kErrorTypeValidation, "Cannot split window into itself");
- return;
+ api_set_error(err, kErrorTypeException, "Cannot split window into itself");
+ goto restore_curwin;
}
- // Set the parent to whatever the correct
- // neighbor window was determined to be.
+ // Set the parent to whatever the correct neighbor window was determined to be.
parent = neighbor;
} else {
- int dir;
- new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp);
- }
- // move to neighboring window if we're moving the current window to a new tabpage
- if (curwin == win && parent != NULL && new_curwin != NULL
- && win_tp != win_find_tabpage(parent)) {
- win_enter(new_curwin, true);
+ altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr);
}
- win_remove(win, win_tp == curtab ? NULL : win_tp);
} else {
- win_remove(win, win_tp == curtab ? NULL : win_tp);
- ui_comp_remove_grid(&win->w_grid_alloc);
- if (win->w_config.external) {
- for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
- if (tp == curtab) {
- continue;
- }
- if (tp->tp_curwin == win) {
- tp->tp_curwin = tp->tp_firstwin;
- }
- }
- }
- win->w_pos_changed = true;
+ altwin = win_float_find_altwin(win, win_tp == curtab ? NULL : win_tp);
}
- int flags = win_split_flags(fconfig.split, parent == NULL);
+ win_remove(win, win_tp == curtab ? NULL : win_tp);
+ if (win_tp == curtab) {
+ last_status(false); // may need to remove last status line
+ win_comp_pos(); // recompute window positions
+ }
- if (parent == NULL) {
- if (!win_split_ins(0, flags, win, 0)) {
- // TODO(willothy): What should this error message say?
- api_set_error(err, kErrorTypeException, "Failed to split window");
- return;
- }
- } else {
- win_execute_T args;
+ int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER;
+ tabpage_T *const parent_tp = parent ? win_find_tabpage(parent) : curtab;
- tabpage_T *tp = win_find_tabpage(parent);
- if (!win_execute_before(&args, parent, tp)) {
- // TODO(willothy): how should we handle this / what should the message be?
- api_set_error(err, kErrorTypeException, "Failed to switch to tabpage %d", tp->handle);
- win_execute_after(&args);
- return;
+ TRY_WRAP(err, {
+ const bool need_switch = parent != NULL && parent != curwin;
+ switchwin_T switchwin;
+ if (need_switch) {
+ // `parent` is valid in its tabpage, so switch_win should not fail.
+ const int result = switch_win(&switchwin, parent, parent_tp, true);
+ (void)result;
+ assert(result == OK);
}
- // This should return the same ptr to `win`, but we check for
- // NULL to detect errors.
- win_T *res = win_split_ins(0, flags, win, 0);
- win_execute_after(&args);
- if (!res) {
- // TODO(willothy): What should this error message say?
- api_set_error(err, kErrorTypeException, "Failed to split window");
- return;
+ to_split_ok = win_split_ins(0, flags, win, 0, unflat_altfr) != NULL;
+ if (!to_split_ok) {
+ // Restore `win` to the window list now, so it's valid for restore_win (if used).
+ win_append(win->w_prev, win, win_tp == curtab ? NULL : win_tp);
+ }
+ if (need_switch) {
+ restore_win(&switchwin, true);
+ }
+ });
+ if (!to_split_ok) {
+ if (was_split) {
+ // win_split_ins doesn't change sizes or layout if it fails to insert an existing window, so
+ // just undo winframe_remove.
+ winframe_restore(win, dir, unflat_altfr);
+ }
+ if (!ERROR_SET(err)) {
+ api_set_error(err, kErrorTypeException, "Failed to move window %d into split", win->handle);
+ }
+
+restore_curwin:
+ // If `win` was the original curwin, and autocommands didn't move it outside of curtab, be a
+ // good citizen and try to return to it.
+ if (curwin_moving_tp && win_valid(win)) {
+ win_goto(win);
}
+ return;
+ }
+
+ // If `win` moved tabpages and was the curwin of its old one, select a new curwin for it.
+ if (win_tp != parent_tp && win_tp->tp_curwin == win) {
+ win_tp->tp_curwin = altwin;
}
+
if (HAS_KEY_X(config, width)) {
win_setwidth_win(fconfig.width, win);
}
if (HAS_KEY_X(config, height)) {
win_setheight_win(fconfig.height, win);
}
- redraw_later(win, UPD_NOT_VALID);
- return;
} else {
win_config_float(win, fconfig);
win->w_pos_changed = true;
@@ -947,8 +1038,19 @@ static void parse_border_style(Object style, WinConfig *fconfig, Error *err)
}
}
-static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, bool reconf,
- bool new_win, Error *err)
+static void generate_api_error(win_T *wp, const char *attribute, Error *err)
+{
+ if (wp->w_floating) {
+ api_set_error(err, kErrorTypeValidation,
+ "Missing 'relative' field when reconfiguring floating window %d",
+ wp->handle);
+ } else {
+ api_set_error(err, kErrorTypeValidation, "non-float cannot have '%s'", attribute);
+ }
+}
+
+static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fconfig, bool reconf,
+ Error *err)
{
#define HAS_KEY_X(d, key) HAS_KEY(d, win_config, key)
bool has_relative = false, relative_is_win = false, is_split = false;
@@ -973,7 +1075,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
} else if (!config->external) {
if (HAS_KEY_X(config, vertical) || HAS_KEY_X(config, split)) {
is_split = true;
- } else if (new_win) {
+ } else if (wp == NULL) { // new win
api_set_error(err, kErrorTypeValidation,
"Must specify 'relative' or 'external' when creating a float");
return false;
@@ -1007,7 +1109,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
if (HAS_KEY_X(config, row)) {
if (!has_relative || is_split) {
- api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
+ generate_api_error(wp, "row", err);
return false;
}
fconfig->row = config->row;
@@ -1015,7 +1117,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
if (HAS_KEY_X(config, col)) {
if (!has_relative || is_split) {
- api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
+ generate_api_error(wp, "col", err);
return false;
}
fconfig->col = config->col;
@@ -1023,7 +1125,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
if (HAS_KEY_X(config, bufpos)) {
if (!has_relative || is_split) {
- api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
+ generate_api_error(wp, "bufpos", err);
return false;
} else {
if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) {
@@ -1065,17 +1167,32 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
}
if (relative_is_win || is_split) {
+ if (reconf && relative_is_win) {
+ win_T *target_win = find_window_by_handle(config->win, err);
+ if (!target_win) {
+ return false;
+ }
+
+ if (target_win == wp) {
+ api_set_error(err, kErrorTypeException, "floating window cannot be relative to itself");
+ return false;
+ }
+ }
fconfig->window = curwin->handle;
if (HAS_KEY_X(config, win)) {
if (config->win > 0) {
fconfig->window = config->win;
}
}
- } else if (has_relative) {
- if (HAS_KEY_X(config, win)) {
+ } else if (HAS_KEY_X(config, win)) {
+ if (has_relative) {
api_set_error(err, kErrorTypeValidation,
"'win' key is only valid with relative='win' and relative=''");
return false;
+ } else if (!is_split) {
+ api_set_error(err, kErrorTypeValidation,
+ "non-float with 'win' requires at least 'split' or 'vertical'");
+ return false;
}
}
@@ -1186,8 +1303,8 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo
}
if (HAS_KEY_X(config, noautocmd)) {
- if (!new_win) {
- api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'");
+ if (wp) {
+ api_set_error(err, kErrorTypeValidation, "'noautocmd' cannot be used with existing windows");
return false;
}
fconfig->noautocmd = config->noautocmd;
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index ed51eedf1b..54a19513db 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -61,11 +61,17 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
if (!win || !buf) {
return;
}
+
+ if (win->w_p_wfb) {
+ api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
+ return;
+ }
+
if (win == cmdwin_win || win == cmdwin_old_curwin || buf == cmdwin_buf) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return;
}
- win_set_buf(win, buf, false, err);
+ win_set_buf(win, buf, err);
}
/// Gets the (1,0)-indexed, buffer-relative cursor position for a given window
@@ -132,7 +138,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
win->w_cursor.col = (colnr_T)col;
win->w_cursor.coladd = 0;
// When column is out of range silently correct it.
- check_cursor_col_win(win);
+ check_cursor_col(win);
// Make sure we stick in this column.
win->w_set_curswant = true;
@@ -142,7 +148,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
switchwin_T switchwin;
switch_win(&switchwin, win, NULL, true);
update_topline(curwin);
- validate_cursor();
+ validate_cursor(curwin);
restore_win(&switchwin, true);
redraw_later(win, UPD_VALID);