From bcb70eeac48040fd6d6bfc20cf7fb6f41374a67c Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 00:42:36 +0000 Subject: fix(api): win_set_config autocmds crash when moving win to other tabpage Problem: win_enter autocommands can close new_curwin, crashing if it was the last window in its tabpage after removing win, or can close parent, crashing when attempting to split it later. Solution: remove win first, check that parent is valid after win_enter. NOTE: This isn't actually quite right, as this means win is not in the window list or even has a frame when triggering enter autocommands (so it's not considered valid in the tabpage). This is addressed in later commits. --- src/nvim/api/win_config.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3cc520dc78..bb1117b3fe 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -478,12 +478,17 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int dir; new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); } + win_remove(win, 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); + if (!win_valid_any_tab(parent)) { + // win_enter autocommands closed the `parent` to split from. + api_set_error(err, kErrorTypeException, "Window to split was closed"); + return; + } } - 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); -- cgit From 233649bc757743f7677b2ae414779192a94aa2ae Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 01:50:49 +0000 Subject: fix(api): win_set_config fires unnecessary autocmds Problem: win_set_config should have the observable effect of moving an existing window to another place, but instead fires autocommands as if a new window was created and entered (and does not fire autocommands reflecting a "return" to the original window). Solution: do not fire win_enter-related autocommands when splitting the window, but continue to fire them when entering the window that fills the new space when moving a window to a different tabpage, as the new curwin changes. Also, remove "++once" from the WinEnter autocmd in the other test, as omitting it also crashed Nvim before this fix. --- src/nvim/api/win_config.c | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index bb1117b3fe..e53e13e2a3 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -505,7 +505,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) win->w_pos_changed = true; } - int flags = win_split_flags(fconfig.split, parent == NULL); + int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; if (parent == NULL) { if (!win_split_ins(0, flags, win, 0)) { @@ -514,24 +514,13 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; } } else { - win_execute_T args; - - 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; - } - // 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; - } + switchwin_T switchwin; + // `parent` is valid in its tabpage, so switch_win should not fail. + const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); + (void)result; + assert(result == OK); + win_split_ins(0, flags, win, 0); + restore_win(&switchwin, true); } if (HAS_KEY_X(config, width)) { win_setwidth_win(fconfig.width, win); @@ -539,7 +528,6 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) 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); -- cgit From a873f33993ef84e3f954127038e559e1ac1cac43 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 02:59:26 +0000 Subject: fix(api): open_win fire BufWinEnter for other buffer when !enter && !noautocmd Problem: BufWinEnter is not fired when not entering a new window, even when a different buffer is specified and buffer-related autocommands are unblocked (!noautocmd). Solution: fire it in the context of the new window and buffer. Do not do it if the buffer is unchanged, like :{s}buffer. Be wary of autocommands! For example, it's possible for nvim_win_set_config to be used in an autocommand to move a window to a different tabpage (in contrast, things like wincmd T actually create a *new* window, so it may not have been possible before, meaning other parts of Nvim could assume windows can't do this... I'd be especially cautious of logic that restores curwin and curtab without checking if curwin is still valid in curtab, if any such logic exists). Also, bail early from win_set_buf if setting the temp curwin fails; this shouldn't be possible, as the callers check that wp is valid, but in case that's not true, win_set_buf will no longer continue setting a buffer for the wrong window. Note that pum_create_float_preview also uses win_set_buf, but from a glance, doesn't look like it properly checks for autocmds screwing things up (win_enter, nvim_create_buf...). I haven't addressed that here. Also adds some test coverage for nvim_open_win autocommands. Closes #27121. --- src/nvim/api/win_config.c | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index e53e13e2a3..9d63a1997c 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -261,8 +261,8 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err 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); + (void)result; wp = win_split_ins(0, flags, NULL, 0); restore_win(&switchwin, true); } @@ -276,18 +276,41 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err api_set_error(err, kErrorTypeException, "Failed to create window"); return 0; } + + // 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. switchwin_T switchwin; - if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { - apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); + { + 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 && buf != wp->w_buffer) { + const bool noautocmd = !enter || fconfig.noautocmd; + win_set_buf(wp, buf, noautocmd, err); + if (!noautocmd) { + tp = win_find_tabpage(wp); + } + // win_set_buf autocommands were blocked if we didn't enter, but we still want BufWinEnter. + if (noautocmd && !fconfig.noautocmd && wp->w_buffer == buf) { + const int result = switch_win_noblock(&switchwin, wp, tp, true); + assert(result == OK); + (void)result; + if (apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, false, buf)) { + tp = win_find_tabpage(wp); + } + restore_win_noblock(&switchwin, true); + } } - if (!win_valid_any_tab(wp)) { + if (!tp) { api_set_error(err, kErrorTypeException, "Window was closed immediately"); return 0; } -- cgit From e55a502ed413d2bc8954b5227acfb34c8689f979 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 18:45:56 +0000 Subject: fix(api): open_win fire Buf* events when !enter && !noautocmd if entered early Problem: if switch_win{_noblock} fails to restore the old curwin after WinNew (e.g: it was closed), wp will become the new curwin, but win_set_buf enter events would still be blocked if !enter && !noautocmd. Solution: fire them, as we've actually entered the new window. Note: there's a problem of switch_win{_noblock} failing to restore the old curwin, leaving us in wp without triggering WinEnter/WinLeave, but this affects all callers of switch_win{_noblock} anyways. (It's also not clear how WinLeave can be called if the old curwin was closed already). --- src/nvim/api/win_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 9d63a1997c..238ec5df1e 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -294,7 +294,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err tp = win_find_tabpage(wp); } if (tp && buf != wp->w_buffer) { - const bool noautocmd = !enter || fconfig.noautocmd; + const bool noautocmd = curwin != wp || fconfig.noautocmd; win_set_buf(wp, buf, noautocmd, err); if (!noautocmd) { tp = win_find_tabpage(wp); -- cgit From b1e24f240baeea80dcf4a3d8453fed0230fb88fd Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 20:15:47 +0000 Subject: fix(api): avoid open_win UAF if target buf deleted by autocmds Problem: WinNew and win_enter autocommands can delete the target buffer to switch to, causing a heap-use-after-free. Solution: store a bufref to the buffer, check it before attempting to switch. --- src/nvim/api/win_config.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 238ec5df1e..3959e74af9 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" @@ -279,6 +280,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err // 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); switchwin_T switchwin; { const int result = switch_win_noblock(&switchwin, wp, tp, true); @@ -293,7 +297,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err goto_tabpage_win(tp, wp); tp = win_find_tabpage(wp); } - if (tp && buf != wp->w_buffer) { + if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) { const bool noautocmd = curwin != wp || fconfig.noautocmd; win_set_buf(wp, buf, noautocmd, err); if (!noautocmd) { -- cgit From 5d58136cccc760f6d95eb45b46f2ad60f06b103b Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 17:17:44 +0000 Subject: fix(api): make open_win/win_set_config check if splitting allowed Problem: splitting is disallowed in some cases to prevent the window layout changes while a window is closing, but it's not checked for. Solution: check for this, and set the API error message directly. (Also sneak in a change to tui.c that got lost from #27352; it's a char* buf, and the memset is assuming one byte each anyway) --- src/nvim/api/win_config.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3959e74af9..557c2f37f9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -246,6 +246,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } } + if (!check_split_disallowed_err(parent ? parent : curwin, err)) { + return 0; // error already set + } + if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { if (config->vertical) { fconfig.split = p_spr ? kWinSplitRight : kWinSplitLeft; @@ -440,6 +444,10 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; } + if (!check_split_disallowed_err(win, err)) { + return; // error already set + } + if (was_split) { win_T *new_curwin = NULL; -- cgit From b1577d371a6db43222de9e3a525def82320ebdb1 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 21:44:42 +0000 Subject: fix(api): make win_set_config with "win" for splits need "split/vertical" Problem: currently, for splits, nvim_win_set_config accepts win without any of split or vertical set, which has little effect and seems error-prone. Solution: require at least one of split or vertical to also be set for splits. Also, update nvim_win_set_config docs, as it's no longer limited to just floating and external windows. --- src/nvim/api/win_config.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 557c2f37f9..21d6d59b1e 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -361,11 +361,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()| /// @@ -1099,11 +1099,15 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo 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; } } -- cgit From a70eae57bd44208a77b5ac29839e8a39ab3c9cd8 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 22:53:37 +0000 Subject: fix(api): make open_win block only enter/leave events if !enter && !noautocmd Problem: nvim_open_win blocking all win_set_buf autocommands when !enter && !noautocmd is too aggressive. Solution: temporarily block WinEnter/Leave and BufEnter/Leave events when setting the buffer. Delegate the firing of BufWinEnter back to win_set_buf, which also has the advantage of keeping the timing consistent (e.g: before the epilogue in enter_buffer, which also handles restoring the cursor position if autocommands didn't change it, among other things). Reword the documentation for noautocmd a bit. I pondered modifying do_buffer and callees to allow for BufEnter/Leave being conditionally disabled, but it seems too invasive (and potentially error-prone, especially if new code paths to BufEnter/Leave are added in the future). Unfortunately, doing this has the drawback of blocking ALL such events for the duration, which also means blocking unrelated such events; like if window switching occurs in a ++nested autocmd fired by win_set_buf. If this turns out to be a problem in practice, a different solution specialized for nvim_open_win could be considered. :-) --- src/nvim/api/win_config.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 21d6d59b1e..8608b0dde9 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -199,9 +199,9 @@ /// - 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 autocommands triggered from setting the +/// `buffer` to display are blocked (e.g: |BufEnter|, |BufLeave|, +/// |BufWinEnter|). /// - 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. @@ -302,20 +302,20 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err tp = win_find_tabpage(wp); } if (tp && bufref_valid(&bufref) && buf != wp->w_buffer) { - const bool noautocmd = curwin != wp || fconfig.noautocmd; - win_set_buf(wp, buf, noautocmd, err); - if (!noautocmd) { + // 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, fconfig.noautocmd, err); + if (!fconfig.noautocmd) { tp = win_find_tabpage(wp); } - // win_set_buf autocommands were blocked if we didn't enter, but we still want BufWinEnter. - if (noautocmd && !fconfig.noautocmd && wp->w_buffer == buf) { - const int result = switch_win_noblock(&switchwin, wp, tp, true); - assert(result == OK); - (void)result; - if (apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, false, buf)) { - tp = win_find_tabpage(wp); - } - restore_win_noblock(&switchwin, true); + if (au_no_enter_leave) { + autocmd_no_enter--; + autocmd_no_leave--; } } if (!tp) { -- cgit From 66f331fef7ad3df480bd02f1705e176d1a07c785 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:49:28 +0000 Subject: vim-patch:9.1.0116: win_split_ins may not check available room Problem: win_split_ins has no check for E36 when moving an existing window Solution: check for room and fix the issues in f_win_splitmove() (Sean Dewar) https://github.com/vim/vim/commit/0fd44a5ad81ade342cb54d8984965bdedd2272c8 Omit WSP_FORCE_ROOM, as it's not needed for Nvim's autocmd window, which is floating. Shouldn't be difficult to port later if it's used for anything else. Make win_splitmove continue working for turning floating windows into splits. Move the logic for "unfloating" a float to win_split_ins; unlike splits, no changes to the window layout are needed before calling it, as floats take no room in the window layout and cannot affect the e_noroom check. Add missing tp_curwin-fixing logic for turning external windows into splits, and add a test. NOTE: there are other issues with the way "tabpage independence" is implemented for external windows; namely, some things assume that tp_curwin is indeed a window within that tabpage, and as such, functions like tabpage_winnr and nvim_tabpage_get_win currently don't always work for external windows (with the latter aborting!) Use last_status over frame_add_statusline, as Nvim's last_status already does this for all windows in the current tabpage. Adjust restore_full_snapshot_rec to handle this. This "restore everything" approach is changed in a future commit anyway, so only ensure it's robust enough to just pass tests. Keep check_split_disallowed's current doc comment, as it's actually a bit more accurate here. (I should probably PR Vim to use this one) Allow f_win_splitmove to move a floating "wp" into a split; Nvim supports this. Continue to disallow it from moving the autocommand window into a split (funnily enough, the check wasn't reachable before, as moving a float was disallowed), but now return -1 in that case (win_splitmove also returns FAIL for this, but handling it in f_win_splitmove avoids us needing to switch windows first). Cherry-pick Test_window_split_no_room fix from v9.1.0121. Update nvim_win_set_config to handle win_split_ins failure in later commits. --- src/nvim/api/win_config.c | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 8608b0dde9..c308cadb7c 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -526,18 +526,6 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } } 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; } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; -- cgit From 1c6b693ec1592f9d193fc9cc1bb03e738fb2bef6 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 25 Feb 2024 01:03:26 +0000 Subject: vim-patch:9.1.0118: Use different restoration strategy in win_splitmove Problem: saving and restoring all frames to split-move is overkill now that WinNewPre is not fired when split-moving. Solution: defer the flattening of frames until win_split_ins begins reorganising them, and attempt to restore the layout by undoing our changes. (Sean Dewar) https://github.com/vim/vim/commit/704966c2545897dfcf426dd9ef946aeb6fa80c38 Adjust winframe_restore to account for Nvim's horizontal separators when the global statusline is in use. Add a test. --- src/nvim/api/win_config.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index c308cadb7c..978d8515c8 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -260,7 +260,7 @@ 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); + wp = win_split_ins(0, flags, NULL, 0, NULL); } else { tp = win_find_tabpage(parent); switchwin_T switchwin; @@ -268,7 +268,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err const int result = switch_win(&switchwin, parent, tp, true); assert(result == OK); (void)result; - wp = win_split_ins(0, flags, NULL, 0); + wp = win_split_ins(0, flags, NULL, 0, NULL); restore_win(&switchwin, true); } if (wp) { @@ -495,11 +495,11 @@ 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); + new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); } 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); + neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); new_curwin = neighbor; } else { // There is only one window in the frame, we can't split it. @@ -511,7 +511,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) parent = neighbor; } else { int dir; - new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); + new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, NULL); } win_remove(win, win_tp == curtab ? NULL : win_tp); // move to neighboring window if we're moving the current window to a new tabpage @@ -531,7 +531,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; if (parent == NULL) { - if (!win_split_ins(0, flags, win, 0)) { + if (!win_split_ins(0, flags, win, 0, NULL)) { // TODO(willothy): What should this error message say? api_set_error(err, kErrorTypeException, "Failed to split window"); return; @@ -542,7 +542,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); (void)result; assert(result == OK); - win_split_ins(0, flags, win, 0); + win_split_ins(0, flags, win, 0, NULL); restore_win(&switchwin, true); } if (HAS_KEY_X(config, width)) { -- cgit From d942c2b9432d81e4b509519bd48fa886e37e9ca8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:51:31 +0000 Subject: fix(api): handle win_split_ins failure properly Problem: nvim_win_set_config does not handle failure in win_split_ins properly yet, which can cause all sorts of issues. Also nvim_open_win and nvim_win_set_config do not set the error message to the one from win_split_ins. Solution: handle failure by undoing winframe_remove, like in win_splitmove. Make sure autocommands from switching to the altwin fire within a valid window, and ensure they don't screw things up. Set the error message to that of win_split_ins, if any. Also change a few other small things, including: - adjust win_append to take a tabpage_T * argument, which is more consistent with win_remove (and also allows us to undo a call to win_remove). - allow winframe_restore to restore window positions. Useful if `wp` was in a different tabpage, as a call to win_comp_pos (which only works for the current tabpage) after winframe_restore should no longer be needed. Though enter_tabpage calls win_comp_pos anyway, this has the advantage of ensuring w_winrow/col remains accurate even before entering the tabpage (useful for stuff like win_screenpos, if used on a window in another tabpage). (This change should probably also be PR'd to Vim later, even though it doesn't use winframe_restore for a `wp` in a different tabpage yet). --- src/nvim/api/win_config.c | 147 +++++++++++++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 55 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 978d8515c8..bb28000719 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -259,18 +259,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, NULL); - } 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); - assert(result == OK); - (void)result; - wp = win_split_ins(0, flags, NULL, 0, NULL); - restore_win(&switchwin, true); - } + TRY_WRAP(err, { + if (parent == NULL || parent == curwin) { + wp = win_split_ins(0, flags, NULL, 0, NULL); + } 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); + assert(result == OK); + (void)result; + wp = win_split_ins(0, flags, NULL, 0, NULL); + restore_win(&switchwin, true); + } + }); if (wp) { wp->w_config = fconfig; } @@ -278,7 +280,9 @@ 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"); + if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, "Failed to create window"); + } return 0; } @@ -448,17 +452,47 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) return; // error already set } - if (was_split) { - win_T *new_curwin = NULL; + 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_valid(prevwin) && prevwin != win ? prevwin : firstwin); + } + // 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; + 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) { @@ -494,64 +528,67 @@ 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, NULL); + 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, NULL); - new_curwin = neighbor; + neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } 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, NULL); - } - win_remove(win, 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); - if (!win_valid_any_tab(parent)) { - // win_enter autocommands closed the `parent` to split from. - api_set_error(err, kErrorTypeException, "Window to split was closed"); - return; - } + winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } - } else { - win_remove(win, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; + 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, win_find_tabpage(parent), true); + (void)result; + assert(result == OK); + } + 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); + } - if (parent == NULL) { - if (!win_split_ins(0, flags, win, 0, NULL)) { - // TODO(willothy): What should this error message say? - api_set_error(err, kErrorTypeException, "Failed to split window"); - return; +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); } - } else { - switchwin_T switchwin; - // `parent` is valid in its tabpage, so switch_win should not fail. - const int result = switch_win(&switchwin, parent, win_find_tabpage(parent), true); - (void)result; - assert(result == OK); - win_split_ins(0, flags, win, 0, NULL); - restore_win(&switchwin, true); + return; } + if (HAS_KEY_X(config, width)) { win_setwidth_win(fconfig.width, win); } if (HAS_KEY_X(config, height)) { win_setheight_win(fconfig.height, win); } - return; } else { win_config_float(win, fconfig); win->w_pos_changed = true; -- cgit From e7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:25:44 +0000 Subject: fix(api): patch some cmdwin/textlock holes Problem: there are new ways to escape textlock or break the cmdwin in nvim_win_set_config and nvim_tabpage_set_win. Solution: fix them. Use win_goto to check it in nvim_tabpage_set_win and use the try_start/end pattern like with similar functions such as nvim_set_current_win (which uses the existing msg_list, if set). Careful not to use `wp->handle` when printing the window ID in the error message for nvim_tabpage_set_win, as win_goto autocommands may have freed the window. On a related note, I have a feeling some API functions ought to be checking curbuf_locked... --- src/nvim/api/tabpage.c | 6 +++++- src/nvim/api/win_config.c | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') 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/win_config.c b/src/nvim/api/win_config.c index bb28000719..dab1e4e80b 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -451,6 +451,12 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) 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 -- cgit From 54022a2946aca5de991e7fa1ebc2954340ec20a8 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 01:00:33 +0000 Subject: fix(api): win_set_config update statuslines after removing splits Problem: nvim_win_set_config does not update statuslines after removing a split. Solution: call last_status. Didn't realize this was missing in the original nvim_win_set_config for splits PR. As it can only be done for the current tabpage, do it if win_tp == curtab; enter_tabpage will eventually call last_status anyway when the user enters another tabpage. --- src/nvim/api/win_config.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index dab1e4e80b..32b2156313 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -550,6 +550,10 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } } 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 + } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; TRY_WRAP(err, { -- cgit From b52d15853e89149472c1ecd9cce3a84e4af0785a Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 9 Mar 2024 16:56:32 +0000 Subject: fix(api): win_set_config set tp_curwin of win moved from other tabpage Problem: nvim_win_set_config does not update the tp_curwin of win's original tabpage when moving it to another. Solution: update it if win was the tp_curwin. Add a test. --- src/nvim/api/win_config.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 32b2156313..8b8eca62ca 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -468,7 +468,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int dir; win_goto(winframe_find_altwin(win, &dir, NULL, NULL)); } else { - win_goto(win_valid(prevwin) && prevwin != win ? prevwin : firstwin); + win_goto(win_float_find_altwin(win, NULL)); } // Autocommands may have been a real nuisance and messed things up... @@ -490,6 +490,8 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) 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. @@ -534,10 +536,11 @@ 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. - winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + 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. - neighbor = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + 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, kErrorTypeException, "Cannot split window into itself"); @@ -546,9 +549,12 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) // Set the parent to whatever the correct neighbor window was determined to be. parent = neighbor; } else { - winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); + altwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp, &unflat_altfr); } + } else { + altwin = win_float_find_altwin(win, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); if (win_tp == curtab) { last_status(false); // may need to remove last status line @@ -556,12 +562,14 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) } int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; + tabpage_T *const parent_tp = parent ? win_find_tabpage(parent) : curtab; + 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, win_find_tabpage(parent), true); + const int result = switch_win(&switchwin, parent, parent_tp, true); (void)result; assert(result == OK); } @@ -593,6 +601,11 @@ restore_curwin: 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); } -- cgit From 141182d6c6c06ad56413b81a518ba9b777a0cbe0 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 25 Dec 2023 20:41:09 -0800 Subject: vim-patch:9.1.0147: Cannot keep a buffer focused in a window Problem: Cannot keep a buffer focused in a window (Amit Levy) Solution: Add the 'winfixbuf' window-local option (Colin Kennedy) fixes: vim/vim#6445 closes: vim/vim#13903 https://github.com/vim/vim/commit/215703563757a4464907ead6fb9edaeb7f430bea N/A patch: vim-patch:58f1e5c0893a --- src/nvim/api/vim.c | 5 +++++ src/nvim/api/window.c | 6 ++++++ 2 files changed, 11 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 84a2f24dbc..24ad7d5fbc 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -876,6 +876,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) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index ed51eedf1b..1a80e9ea16 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -61,6 +61,12 @@ 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; -- cgit From ac8cd5368db83cced9bc049ceb50c21cb8a4f743 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 10:44:53 +0800 Subject: refactor: use ml_get_buf_len() in API code (#27825) --- src/nvim/api/buffer.c | 29 ++++++++++++++--------------- src/nvim/api/extmark.c | 16 ++++++++-------- src/nvim/api/private/helpers.c | 8 ++++---- 3 files changed, 26 insertions(+), 27 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 7f195de959..035e36a2dd 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -529,18 +529,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 +563,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 +575,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; } @@ -1324,7 +1322,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; } @@ -1424,6 +1422,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/extmark.c b/src/nvim/api/extmark.c index 1b03a97edb..a21cf5b337 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -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; 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; } -- cgit From 08fc1ebbaa49e3110b65bddeed28d2e61a96f5d9 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 11 Mar 2024 13:19:49 +0100 Subject: fix(api/buffer): fix handling of viewport of non-current buffer A lot of functions in move.c only worked for curwin, alternatively took a `wp` arg but still only work if that happens to be curwin. Refactor those that are needed for update_topline(wp) to work for any window. fixes #27723 fixes #27720 --- src/nvim/api/buffer.c | 11 +++++++---- src/nvim/api/extmark.c | 4 ++-- src/nvim/api/window.c | 4 ++-- 3 files changed, 11 insertions(+), 8 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 035e36a2dd..42467d1562 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1269,10 +1269,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 @@ -1307,7 +1310,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; @@ -1343,7 +1346,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); } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index a21cf5b337..b5f56d270c 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1246,7 +1246,7 @@ 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); + changed_window_setting(win); return true; } @@ -1291,7 +1291,7 @@ 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); + changed_window_setting(win); return true; } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 1a80e9ea16..026d09d9a9 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -138,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; @@ -148,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); -- cgit From c971f538ab87b537ae4c97bd44167661c5691a2d Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 14 Mar 2024 23:55:32 +0100 Subject: fix(api): update grid cursor in nvim_win_set_cursor() Problem: Cursor position set by nvim_win_set_cursor() is not reflected on the screen when followed by a blocking call like getchar(). Solution: Immediately update the cursor position on the grid. --- src/nvim/api/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 026d09d9a9..30f77c7248 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -148,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(curwin); + setcursor_mayforce(true); restore_win(&switchwin, true); redraw_later(win, UPD_VALID); -- cgit From e0707d3529592504fbafaca0d8f052247af789ae Mon Sep 17 00:00:00 2001 From: bfredl Date: Fri, 15 Mar 2024 11:31:41 +0100 Subject: fix(ui): fix edge case around flushing ui_flush_buf() doesn't know about `lenpos` so `remote_ui_raw_line` needs to always handle it before flushing --- src/nvim/api/ui.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 692e3f95fc..35348f344b 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -533,7 +533,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); } @@ -781,11 +782,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 + sizeof(schar_T) + 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 +835,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); -- cgit From d5c23d72a5e4d2abb0903e58c4953fa0303d4ad6 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:55:33 +0000 Subject: fix(api): nvim_create_buf leaks memory if buffer is loaded early Problem: memory leak in nvim_create_buf if buflist_new autocommands load the new buffer early. Solution: do not open a memfile in that case. --- src/nvim/api/vim.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 24ad7d5fbc..7a9094557d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -967,13 +967,15 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) // 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) { - goto fail; + if (buf->b_ml.ml_mfp == NULL) { + try_start(); + block_autocmds(); + int status = ml_open(buf); + unblock_autocmds(); + try_end(err); + if (status == FAIL) { + goto fail; + } } // Set last_changedtick to avoid triggering a TextChanged autocommand right -- cgit From 6091df6b7a0674a7215c5c1d2d93a1b37e9121b5 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 19 Mar 2024 12:59:44 +0000 Subject: fix(api): nvim_create_buf assert fails if autocmds set &swapfile Problem: assertion failure in nvim_create_buf if buflist_new autocommands open a swapfile when "scratch" is set. Solution: block autocommands when setting up the buffer; fire them later instead. Note that, unlike buflist_new, I don't check if autocommands aborted script processing; the buffer is already created and configured at that point, so might as well return the handle anyway. Rather than repeat try_{start,end} and {un}block_autocmds for each relevant operation, just do it at the start and near the end. This means that, if TermResponse fires from unblock_autocmds for whatever reason, it can see the buffer in an already configured state if we didn't bail due to an error (plus it's probably a bit cleaner this way). --- src/nvim/api/vim.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7a9094557d..43bf4eaf31 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -958,24 +958,22 @@ 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. - if (buf->b_ml.ml_mfp == NULL) { - try_start(); - block_autocmds(); - int status = ml_open(buf); + if (ml_open(buf) == FAIL) { unblock_autocmds(); - try_end(err); - if (status == FAIL) { - goto fail; - } + goto fail; } // Set last_changedtick to avoid triggering a TextChanged autocommand right @@ -985,7 +983,7 @@ 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); @@ -996,10 +994,26 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) 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; -- cgit From 2214f9c19daa46a1fc37bcc14c017c092894a506 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Sat, 3 Feb 2024 12:57:03 +0600 Subject: refactor(options): remove `set_string_option_direct()` Problem: `set_string_option_direct()` contains a separate codepath specifically for setting string options. Not only is that unnecessary code duplication, but it's also limited to only string options. Solution: Replace `set_string_option_direct()` with `set_option_direct()` which calls `set_option()` under the hood. This reduces code duplication and allows directly setting an option of any type. --- src/nvim/api/vim.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 43bf4eaf31..2b3ebb7bfb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -988,8 +988,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) 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; -- cgit From a89ce89742db600665b69e58d5e1bc3dbee9d57b Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Thu, 28 Mar 2024 02:32:32 +0100 Subject: docs: fix typos (#27868) Co-authored-by: ite-usagi <77563904+ite-usagi@users.noreply.github.com> Co-authored-by: v-sim <56476039+v-sim@users.noreply.github.com> Co-authored-by: Evgeni Chasnovski Co-authored-by: zeertzjq Co-authored-by: Quico Augustijn Co-authored-by: nhld Co-authored-by: francisco souza <108725+fsouza@users.noreply.github.com> --- src/nvim/api/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 42467d1562..b32159dc96 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -968,7 +968,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 -- cgit From 2e4e12756a697d4767ec294e1f268384395e7a7f Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 28 Mar 2024 15:27:32 +0100 Subject: feat(ui): indicate margins for the area used by win_viewport Problem: using win_viewport for implementing smooth scrolling in an external UI might run into problems when winbar or borders is used, as there is no indication that the entire grid is not used for scrolled buffer text. Solution: add `win_viewport_margins` event. --- src/nvim/api/ui_events.in.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/nvim/api') 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; -- cgit From b25753381c6049132c5c8d02eb62df99f8a958fd Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 31 Mar 2024 20:21:47 +0800 Subject: fix(api): set script context when using nvim_set_hl (#28123) --- src/nvim/api/vim.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 2b3ebb7bfb..2fb8f3d554 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -167,7 +167,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 +184,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); + }); } } -- cgit From b3f9da9524b0b1bb992d3dd84eaa92980923cb3e Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Mon, 1 Apr 2024 21:48:21 -0700 Subject: refactor: informative error msg for 'noautocmd' (#28147) Problem: Attempting to set 'noautocmd' for an existing window throws an error that just says "Invalid key: 'noautocmd'" which is not very helpful. Solution: Explain that 'noautocmd' can't be used with existing windows. --- src/nvim/api/win_config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 43cff9b2c3..f16e614c47 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1267,7 +1267,7 @@ 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'"); + api_set_error(err, kErrorTypeValidation, "'noautocmd' cannot be used with existing windows"); return false; } fconfig->noautocmd = config->noautocmd; -- cgit From a500c5f808ccf0b678c935f00e0af4503a5bd724 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 5 Apr 2024 18:04:45 +0800 Subject: vim-patch:8.1.0815: dialog for file changed outside of Vim not tested (#28184) Problem: Dialog for file changed outside of Vim not tested. Solution: Add a test. Move FileChangedShell test. Add 'L' flag to feedkeys(). https://github.com/vim/vim/commit/5e66b42aae7c67a3ef67617d4bd43052ac2b73ce Co-authored-by: Bram Moolenaar --- src/nvim/api/vim.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 2fb8f3d554..82e9ddff2d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -277,6 +277,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]) { @@ -292,6 +293,8 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks) execute = true; break; case '!': dangerous = true; break; + case 'L': + lowlevel = true; break; } } @@ -307,10 +310,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) { -- cgit From 898371fc9faec605b11a17857a862991b722db51 Mon Sep 17 00:00:00 2001 From: glepnir Date: Thu, 28 Dec 2023 17:46:59 +0800 Subject: fix(float): don't relative flaot win itself Problem: when reconfig current float win without win key in nvim_win_set_config will cause float win position changed when move. Solution: don't relative itself. --- src/nvim/api/win_config.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index f16e614c47..184efd3647 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -225,7 +225,7 @@ 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_float_config(NULL, config, &fconfig, false, err)) { return 0; } @@ -396,7 +396,7 @@ 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_float_config(win, config, &fconfig, !was_split || to_split, err)) { return; } if (was_split && !to_split) { @@ -1023,8 +1023,8 @@ 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 bool parse_float_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; @@ -1049,7 +1049,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; @@ -1141,6 +1141,17 @@ 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) { @@ -1266,7 +1277,7 @@ static bool parse_float_config(Dict(win_config) *config, WinConfig *fconfig, boo } if (HAS_KEY_X(config, noautocmd)) { - if (!new_win) { + if (wp) { api_set_error(err, kErrorTypeValidation, "'noautocmd' cannot be used with existing windows"); return false; } -- cgit From 3ea124a8d9f08dd9f9af3bc0877f7b888a47f10b Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 13 Apr 2024 14:36:17 +0800 Subject: fix(float): improve error message when reconfig failed (#25076) Problem: The current error message isn't very accurate. Solution: Improve the error message. --- src/nvim/api/win_config.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 184efd3647..feb4271b5f 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1023,6 +1023,17 @@ static void parse_border_style(Object style, WinConfig *fconfig, 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_float_config(win_T *wp, Dict(win_config) *config, WinConfig *fconfig, bool reconf, Error *err) { @@ -1083,7 +1094,7 @@ static bool parse_float_config(win_T *wp, Dict(win_config) *config, WinConfig *f 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; @@ -1091,7 +1102,7 @@ static bool parse_float_config(win_T *wp, Dict(win_config) *config, WinConfig *f 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; @@ -1099,7 +1110,7 @@ static bool parse_float_config(win_T *wp, Dict(win_config) *config, WinConfig *f 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)) { -- cgit From 7180ef690180cf92d1d49811820c46dd60e4d1c6 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Mon, 15 Apr 2024 00:10:16 +0100 Subject: feat(api)!: nvim_open_win: noautocmd blocks all autocmds #28192 Problem: noautocmd is confusing; despite its name, it doesn't block all autocommands (instead it blocks only those related to setting the buffer), and is commonly used by plugins to open windows while producing minimal side-effects. Solution: be consistent and block all autocommands when noautocmd is set. This includes WinNew (again), plus autocommands from entering the window (if enter is set) like WinEnter, WinLeave, TabEnter, .etc. See the discussion at https://github.com/neovim/neovim/pull/14659#issuecomment-2040029517 for more information. Remove win_set_buf's noautocmd argument, as it's no longer needed. NOTE: pum_create_float_preview sets noautocmd for win_set_buf, but all its callers already use block_autocmds. Despite that, pum_create_float_preview doesn't actually properly handle autocommands (it has no checks for whether those from win_enter or nvim_create_buf free the window). For now, ensure autocommands are blocked within it for correctness (in case it's ever called outside of a block_autocmds context; the function seems to have been refactored in #26739 anyway). --- src/nvim/api/win_config.c | 33 +++++++++++++++++++++------------ src/nvim/api/window.c | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index feb4271b5f..11b6b17516 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -199,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 autocommands triggered from setting the -/// `buffer` to display are blocked (e.g: |BufEnter|, |BufLeave|, -/// |BufWinEnter|). +/// - 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. @@ -230,6 +229,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } 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; @@ -239,15 +242,15 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err parent = find_window_by_handle(fconfig.window, err); if (!parent) { // find_window_by_handle has already set the error - return 0; + goto cleanup; } else if (parent->w_floating) { api_set_error(err, kErrorTypeException, "Cannot split a floating window"); - return 0; + goto cleanup; } } if (!check_split_disallowed_err(parent ? parent : curwin, err)) { - return 0; // error already set + goto cleanup; // error already set } if (HAS_KEY_X(config, vertical) && !HAS_KEY_X(config, split)) { @@ -283,7 +286,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err if (!ERROR_SET(err)) { api_set_error(err, kErrorTypeException, "Failed to create window"); } - return 0; + goto cleanup; } // Autocommands may close `wp` or move it to another tabpage, so update and check `tp` after each @@ -291,8 +294,8 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err // Also, autocommands may free the `buf` to switch to, so store a bufref to check. bufref_T bufref; set_bufref(&bufref, buf); - switchwin_T switchwin; - { + if (!fconfig.noautocmd) { + switchwin_T switchwin; const int result = switch_win_noblock(&switchwin, wp, tp, true); assert(result == OK); (void)result; @@ -313,7 +316,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err autocmd_no_enter++; autocmd_no_leave++; } - win_set_buf(wp, buf, fconfig.noautocmd, err); + win_set_buf(wp, buf, err); if (!fconfig.noautocmd) { tp = win_find_tabpage(wp); } @@ -324,14 +327,20 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } 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 } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 30f77c7248..08ecca1380 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -71,7 +71,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) 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 -- cgit From 4ec8fd43bfdf1924ee03e07afc8a46dfdd3c9b12 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 15 Apr 2024 17:55:57 +0800 Subject: fix(api): make width/height for split by nvim_open_win work (#28341) --- src/nvim/api/win_config.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 11b6b17516..3bc9cd816f 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -224,7 +224,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err } WinConfig fconfig = WIN_CONFIG_INIT; - if (!parse_float_config(NULL, config, &fconfig, false, err)) { + if (!parse_win_config(NULL, config, &fconfig, false, err)) { return 0; } @@ -263,8 +263,9 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err int flags = win_split_flags(fconfig.split, parent == NULL) | WSP_NOENTER; TRY_WRAP(err, { + int size = (flags & WSP_VERT) ? fconfig.width : fconfig.height; if (parent == NULL || parent == curwin) { - wp = win_split_ins(0, flags, NULL, 0, NULL); + wp = win_split_ins(size, flags, NULL, 0, NULL); } else { tp = win_find_tabpage(parent); switchwin_T switchwin; @@ -272,7 +273,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err const int result = switch_win(&switchwin, parent, tp, true); assert(result == OK); (void)result; - wp = win_split_ins(0, flags, NULL, 0, NULL); + wp = win_split_ins(size, flags, NULL, 0, NULL); restore_win(&switchwin, true); } }); @@ -405,7 +406,7 @@ 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(win, config, &fconfig, !was_split || to_split, err)) { + if (!parse_win_config(win, config, &fconfig, !was_split || to_split, err)) { return; } if (was_split && !to_split) { @@ -1043,8 +1044,8 @@ static void generate_api_error(win_T *wp, const char *attribute, Error *err) } } -static bool parse_float_config(win_T *wp, Dict(win_config) *config, WinConfig *fconfig, bool reconf, - Error *err) +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; -- cgit From 47ba96a6b3bf22097134b319ed97ec840b05eaa7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 11:59:55 +0800 Subject: test: getting autocmd Lua callback in Vimscript (#28367) Also remove unnecessary variable in API converter. --- src/nvim/api/private/converter.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/nvim/api') 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); \ } \ -- cgit From 7fa24948a936a95519f0c8c496402488b6508c14 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 14:05:09 +0800 Subject: test: make mapping tests more consistent (#28368) - Test maparg() and maplist() in the same test. - Use matches() instead of string.match(). - Avoid overlong lines and strange spacing in exec_lua(). - Revert code change from last PR as the variable may be needed. --- src/nvim/api/private/converter.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index a78d78c057..a70ef1e50b 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -76,7 +76,8 @@ 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)) { \ - kvi_push(edata->stack, LUAREF_OBJ(api_new_luaref(fp->uf_luaref))); \ + LuaRef ref = api_new_luaref(fp->uf_luaref); \ + kvi_push(edata->stack, LUAREF_OBJ(ref)); \ } else { \ TYPVAL_ENCODE_CONV_NIL(tv); \ } \ -- cgit From 5cfdaaaeac0f53a621696d8eb6b5a3ba90438c98 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 20:57:01 +0800 Subject: fix(api): ignore 'autochdir' when renaming other buf (#28376) Problem: Renaming non-current buffer changes working directory when 'autochdir' is set. Solution: Temporarily disable 'autochdir'. Add more tests for the win_set_buf change. --- src/nvim/api/buffer.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index b32159dc96..452ba49e04 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" @@ -984,12 +985,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; } -- cgit From 329fc0e5b7f7777c405e4828650567a93620ba50 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 17 Apr 2024 06:34:10 +0800 Subject: test: API can return Lua function to Lua code (#28380) --- src/nvim/api/private/converter.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/nvim/api') 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); \ } \ -- cgit From 344906a08f0972108eb912c87af32b275ecf318e Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Sun, 21 Apr 2024 02:15:18 +0200 Subject: fix(api): do not update grid position in nvim_win_set_cursor (#28235) Revert commit c971f538ab87b537ae4c97bd44167661c5691a2d. Forcing grid cursor position will need a new API like originally proposed in #27858. --- src/nvim/api/window.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 08ecca1380..54a19513db 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -148,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); - setcursor_mayforce(true); + validate_cursor(curwin); restore_win(&switchwin, true); redraw_later(win, UPD_VALID); -- cgit From fb5e2db4c72251ac8f59a7b80fc6d6b40dbec1d8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 22 Apr 2024 04:28:16 -0700 Subject: refactor(api): deprecate nvim_call_atomic #28433 TODO: FUNC_API_REMOTE_ONLY APIs such as `nvim_ui_*` cannot (yet) be used in `nvim_exec_lua`. We can change FUNC_API_REMOTE_ONLY to allow Vimscript/Lua to pass an explicit `channel_id`. #28437 --- src/nvim/api/deprecated.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++ src/nvim/api/vim.c | 84 -------------------------------------------- 2 files changed, 89 insertions(+), 84 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index 6254e9fbd8..d63b8411e8 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,89 @@ 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; +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 82e9ddff2d..d62ea46e1b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1698,90 +1698,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. -- cgit From 16513b30337523dc64707309ee7fe3dd2247266d Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Wed, 24 Apr 2024 18:14:05 -0700 Subject: feat(api): allow floats to be opened in non-current tabpage (#28480) \ --- src/nvim/api/win_config.c | 53 ++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 24 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3bc9cd816f..3a9986a7d1 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -236,19 +236,19 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err 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 - goto cleanup; - } else if (parent->w_floating) { - api_set_error(err, kErrorTypeException, "Cannot split a floating window"); - goto cleanup; - } - } - if (!check_split_disallowed_err(parent ? parent : curwin, err)) { goto cleanup; // error already set } @@ -267,7 +267,6 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(win_config) *config, Err if (parent == NULL || parent == curwin) { wp = win_split_ins(size, flags, NULL, 0, NULL); } 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); @@ -395,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); @@ -409,23 +409,28 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *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) { -- cgit From 0df681a91d6b86395609e6fc40efb4d8623d72c9 Mon Sep 17 00:00:00 2001 From: bfredl Date: Mon, 29 Apr 2024 14:12:39 +0200 Subject: fix(treesitter): make tests for memoize more robust Instead of painfully messing with timing to determine if queries were reparsed, we can simply keep a counter next to the call to ts_query_new Also memoization had a hidden dependency on the garbage collection of the the key, a hash value which never is kept around in memory. this was done intentionally as the hash does not capture all relevant state for the query (external included files) even if actual query objects still would be reachable in memory. To make the test fully deterministic in CI, we explicitly control GC. --- src/nvim/api/vim.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d62ea46e1b..e4e4ae29f2 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -45,6 +45,7 @@ #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" @@ -1806,12 +1807,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; } -- cgit From 71cf75f96a67aeb79ac3af6aa829bac81bd2d33d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 30 Apr 2024 04:30:21 -0700 Subject: docs: misc #24163 - Also delete old perl scripts which are not used since 8+ years ago. fix #23251 fix #27367 ref https://github.com/neovim/neovim/issues/2252#issuecomment-1902662577 Helped-by: Daniel Kongsgaard Co-authored-by: Kevin Pham --- src/nvim/api/autocmd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') 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 [] /// - buf: (number) expanded value of [] /// - file: (string) expanded value of [] -/// - 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 -- cgit From 0330dd9e69de7567fd2479c0203b778a1d2dce2f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 30 Apr 2024 05:12:51 -0700 Subject: fix(api): mark nvim__complete_set as experimental #28579 Problem: nvim_complete_set was added in 5ed55ff14c8b7e346811cb6228bf63fb5106bae9 but needs more bake time. Solution: Rename it, mark it as experimental. --- src/nvim/api/vim.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index e4e4ae29f2..8458611ee8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2282,20 +2282,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)) { -- cgit From dafa51c16d9bdaec5011b591b0ce8dff287624b7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 30 Apr 2024 06:06:14 -0700 Subject: docs(api): sort unreleased nvim__ functions last #28580 --- src/nvim/api/vim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 8458611ee8..e75f4e629b 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2282,7 +2282,7 @@ 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 : ""); } -/// EXPERIMENTAL: this api may change in the future. +/// EXPERIMENTAL: this API may change in the future. /// /// 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. -- cgit From 0b8a72b73934d33a05e20c255298e88cd921df32 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 1 May 2024 08:08:22 -0500 Subject: revert: "feat(extmarks): subpriorities (relative to declaration order) (#27131)" (#28585) This reverts commit 15e77a56b711102fdc123e15b3f37d49bc0b1df1. Subpriorities were added in https://github.com/neovim/neovim/pull/27131 as a mechanism for enforcing query order when using iter_matches in the Tree-sitter highlighter. However, iter_matches proved to have too many complications to use in the highlighter so we eventually reverted back to using iter_captures (https://github.com/neovim/neovim/pull/27901). Thus, subpriorities are no longer needed and can be removed. --- src/nvim/api/extmark.c | 20 ++++---------------- src/nvim/api/keysets_defs.h | 2 -- 2 files changed, 4 insertions(+), 18 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index b5f56d270c..60e12e9da8 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -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) { diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index fe91d9760d..7c5fddff55 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 { -- cgit From e778e0116198470ba037b9426f4ff7fa5cb7f880 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Wed, 1 May 2024 22:51:06 +0200 Subject: fix(ui): avoid recursiveness and invalid memory access #28578 Problem: Calling :redraw from vim.ui_attach() callback results in recursive cmdline/message events. Solution: Avoid recursiveness where possible and replace global "call_buf" with separate, temporary buffers for each event so that when a Lua callback for one event fires another event, that does not result in invalid memory access. --- src/nvim/api/ui.c | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 35348f344b..93be51e458 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); @@ -583,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)); } @@ -593,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 { @@ -609,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)); @@ -619,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)); @@ -647,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)); @@ -657,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); } @@ -678,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); @@ -706,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); } @@ -722,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)); @@ -742,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); @@ -751,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); } @@ -950,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; @@ -972,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; } -- cgit From 037ea6e786b5d05f4a8965e4c2ba6aa60ec7c01a Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Wed, 10 Apr 2024 11:42:46 +0200 Subject: feat(api): add nvim__redraw for more granular redrawing Experimental and subject to future changes. Add a way to redraw certain elements that are not redrawn while Nvim is waiting for input, or currently have no API to do so. This API covers all that can be done with the :redraw* commands, in addition to the following new features: - Immediately move the cursor to a (non-current) window. - Target a specific window or buffer to mark for redraw. - Mark a buffer range for redraw (replaces nvim__buf_redraw_range()). - Redraw the 'statuscolumn'. --- src/nvim/api/buffer.c | 14 ---- src/nvim/api/keysets_defs.h | 14 ++++ src/nvim/api/vim.c | 156 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 14 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 452ba49e04..7e64808709 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -230,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 diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 7c5fddff55..00d8aa8428 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -373,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/vim.c b/src/nvim/api/vim.c index e75f4e629b..d8ebc4b94f 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -50,6 +50,7 @@ #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" @@ -2305,3 +2306,158 @@ Dictionary nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *ar } 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; + if (last < 0) { + last = buf->b_ml.ml_line_count; + } + redraw_buf_range_later(win ? win->w_buffer : (buf ? buf : curbuf), 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; +} -- cgit From cf9f002f31c8b4d9d42912a3f45f5d3db4462fd9 Mon Sep 17 00:00:00 2001 From: luukvbaal Date: Fri, 3 May 2024 04:35:32 +0200 Subject: fix(api): use correct buffer for "range" in nvim__redraw (#28614) --- src/nvim/api/vim.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d8ebc4b94f..f5b7c8abdd 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -2415,10 +2415,11 @@ void nvim__redraw(Dict(redraw) *opts, Error *err) }); 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; - if (last < 0) { - last = buf->b_ml.ml_line_count; + buf_T *rbuf = win ? win->w_buffer : (buf ? buf : curbuf); + if (last == -1) { + last = rbuf->b_ml.ml_line_count; } - redraw_buf_range_later(win ? win->w_buffer : (buf ? buf : curbuf), first, last); + redraw_buf_range_later(rbuf, first, last); } if (opts->cursor) { -- cgit From d44ed3a885e163df33cce8180ca9f72fb5c0661a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 3 May 2024 18:02:25 +0800 Subject: perf(extmarks): better track whether namespace has extmarks (#28615) This avoids redraw when adding/removing an empty namespace for a window. This also avoids marktree traversal when clearing a namespace that has already been cleared, which is added as a benchmark. --- src/nvim/api/extmark.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 60e12e9da8..320e14b654 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1234,7 +1234,9 @@ 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); + if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) { + changed_window_setting(win); + } return true; } @@ -1279,7 +1281,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); + if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) { + changed_window_setting(win); + } return true; } -- cgit From 4e5c633ed4871a948aff7338b793ac5f93484153 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 12 May 2024 05:39:33 +0800 Subject: fix(api): make getting explicit empty hl in virtual text work (#28697) --- src/nvim/api/extmark.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 320e14b654..f7c3788dad 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)); @@ -1165,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) { -- cgit From 91a4938edfd3c0382746a8b7b72cb97ab4a6c7ef Mon Sep 17 00:00:00 2001 From: bfredl Date: Sun, 12 May 2024 12:04:18 +0200 Subject: fix(ui): data corruption in remote_ui_raw_line This particular repro is quite niche but there could be other cases, whenever the the second last cell plus the "fill" cell togheter are too complex --- src/nvim/api/ui.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 93be51e458..fdf25c75d7 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -778,7 +778,7 @@ 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. -- cgit From 8f0a166da4cd919947ef1ed634d350ef602acc63 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 20 Apr 2024 23:21:08 +0200 Subject: refactor(api): rename nvim_win_remove_ns Problem: nvim_win_remove_ns does not follow `help dev-naming` API naming conventions. Solution: Rename it. --- src/nvim/api/extmark.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index f7c3788dad..ef40daf7bb 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -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 @@ -1215,10 +1215,11 @@ 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. +/// 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) @@ -1241,7 +1242,7 @@ Boolean nvim_win_add_ns(Window window, Integer ns_id, Error *err) return true; } -/// Gets all the namespaces scopes associated with a window. +/// Gets the namespace scopes for a given window. /// /// @param window Window handle, or 0 for current window /// @return a list of namespaces ids @@ -1262,12 +1263,12 @@ ArrayOf(Integer) nvim_win_get_ns(Window window, Arena *arena, Error *err) return rv; } -/// Removes the namespace scope from the window. +/// 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) +Boolean nvim_win_del_ns(Window window, Integer ns_id, Error *err) FUNC_API_SINCE(12) { win_T *win = find_window_by_handle(window, err); -- cgit From 97c7646501d5cd6f57c57ce30acca89c5b8573ff Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 12 May 2024 23:12:25 +0200 Subject: refactor(api): nvim_win_xx_ns are EXPERIMENTAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: The nvim_win_xx_ns function family introduced in ba0370b1d718d473d0ef51c35d88b98ba220082b needs more bake-time. Currently it's narrowly defined for windows, but other scopes ("buffer") and features are likely in the future. Solution: - Rename the API with double-underscore to mark it as EXPERIMENTAL. TODO/FUTURE: - Rename and change the signature to support more than just "window" scope, and for other flexibility. - Open question: we could choose either: - "store scopes on namespaces", or - "store namespaces on scopes (w:/b:/…)" --- src/nvim/api/extmark.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index ef40daf7bb..85cce45560 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -490,7 +490,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - 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 (EXPERIMENTAL) enables "scoping" for the extmark. See -/// |nvim_win_add_ns()| +/// |nvim__win_add_ns()| /// /// @param[out] err Error details, if any /// @return Id of the created/updated extmark @@ -1215,14 +1215,15 @@ String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error return mt_inspect(buf->b_marktree, keys, dot); } +/// 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 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) { @@ -1242,12 +1243,13 @@ Boolean nvim_win_add_ns(Window window, Integer ns_id, Error *err) return true; } +/// 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) { @@ -1263,13 +1265,14 @@ ArrayOf(Integer) nvim_win_get_ns(Window window, Arena *arena, Error *err) return rv; } +/// 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_del_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) { -- cgit From aec4938a21a02d279d13a9eb64ef3b7cc592c374 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 17 May 2024 07:37:39 -0700 Subject: feat(api): broadcast events to ALL channels #28487 Problem: `vim.rpcnotify(0)` and `rpcnotify(0)` are documented as follows: If {channel} is 0, the event is broadcast to all channels. But that's not actually true. Channels must call `nvim_subscribe` to receive "broadcast" events, so it's actually "multicast". - Assuming there is a use-case for "broadcast", the current model adds an extra step for broadcasting: all channels need to "subscribe". - The presence of `nvim_subscribe` is a source of confusion for users, because its name implies something more generally useful than what it does. Presumably the use-case of `nvim_subscribe` is to avoid "noise" on RPC channels not expected a broadcast notification, and potentially an error if the channel client reports an unknown event. Solution: - Deprecate `nvim_subscribe`/`nvim_unsubscribe`. - If applications want to multicast, they can keep their own multicast list. Or they can use `nvim_list_chans()` and `nvim_get_chan_info()` to enumerate and filter the clients they want to target. - Always send "broadcast" events to ALL channels. Don't require channels to "subscribe" to receive broadcasts. This matches the documented behavior of `rpcnotify()`. --- src/nvim/api/deprecated.c | 20 ++++++++++++++++++++ src/nvim/api/vim.c | 30 ------------------------------ 2 files changed, 20 insertions(+), 30 deletions(-) (limited to 'src/nvim/api') diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index d63b8411e8..af3bfe2c03 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -786,3 +786,23 @@ 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/vim.c b/src/nvim/api/vim.c index f5b7c8abdd..fc780e1248 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1334,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. /// -- cgit