diff options
Diffstat (limited to 'src/nvim/api')
-rw-r--r-- | src/nvim/api/autocmd.c | 104 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 351 | ||||
-rw-r--r-- | src/nvim/api/command.c | 8 | ||||
-rw-r--r-- | src/nvim/api/deprecated.c | 223 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 157 | ||||
-rw-r--r-- | src/nvim/api/extmark.h | 1 | ||||
-rw-r--r-- | src/nvim/api/keysets_defs.h | 114 | ||||
-rw-r--r-- | src/nvim/api/options.c | 60 | ||||
-rw-r--r-- | src/nvim/api/private/converter.c | 3 | ||||
-rw-r--r-- | src/nvim/api/private/defs.h | 2 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 89 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 16 | ||||
-rw-r--r-- | src/nvim/api/tabpage.c | 13 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 4 | ||||
-rw-r--r-- | src/nvim/api/ui.h | 1 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 6 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 416 | ||||
-rw-r--r-- | src/nvim/api/vimscript.c | 70 | ||||
-rw-r--r-- | src/nvim/api/win_config.c | 35 | ||||
-rw-r--r-- | src/nvim/api/window.c | 77 |
20 files changed, 900 insertions, 850 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 22932fd1a2..d436dbb7f1 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -2,7 +2,6 @@ #include <lauxlib.h> #include <stdbool.h> #include <stdint.h> -#include <stdio.h> #include <stdlib.h> #include <string.h> @@ -23,6 +22,7 @@ #include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/strings.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" @@ -68,32 +68,31 @@ static int64_t next_autocmd_id = 1; /// match any combination of them. /// /// @param opts Dict with at least one of the following: -/// - group (string|integer): the autocommand group name or id to match against. -/// - event (string|array): event or events to match against |autocmd-events|. -/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|. -/// Cannot be used with {buffer} -/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands +/// - buffer: (integer) Buffer number or list of buffer numbers for buffer local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern} +/// - event: (string|table) event or events to match against |autocmd-events|. +/// - id: (integer) Autocommand ID to match. +/// - group: (string|table) the autocommand group name or id to match against. +/// - pattern: (string|table) pattern or patterns to match against |autocmd-pattern|. +/// Cannot be used with {buffer} /// @return Array of autocommands matching the criteria, with each item /// containing the following fields: -/// - id (number): the autocommand id (only when defined with the API). -/// - group (integer): the autocommand group id. -/// - group_name (string): the autocommand group name. -/// - desc (string): the autocommand description. -/// - event (string): the autocommand event. -/// - command (string): the autocommand command. Note: this will be empty if a callback is set. -/// - callback (function|string|nil): Lua function or name of a Vim script function +/// - buffer: (integer) the buffer number. +/// - buflocal: (boolean) true if the autocommand is buffer local. +/// - command: (string) the autocommand command. Note: this will be empty if a callback is set. +/// - callback: (function|string|nil): Lua function or name of a Vim script function /// which is executed when this autocommand is triggered. -/// - once (boolean): whether the autocommand is only run once. -/// - pattern (string): the autocommand pattern. +/// - desc: (string) the autocommand description. +/// - event: (string) the autocommand event. +/// - id: (integer) the autocommand id (only when defined with the API). +/// - group: (integer) the autocommand group id. +/// - group_name: (string) the autocommand group name. +/// - once: (boolean) whether the autocommand is only run once. +/// - pattern: (string) the autocommand pattern. /// If the autocommand is buffer local |autocmd-buffer-local|: -/// - buflocal (boolean): true if the autocommand is buffer local. -/// - buffer (number): the buffer number. Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) FUNC_API_SINCE(9) { - // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) - ArrayBuilder autocmd_list = KV_INITIAL_VALUE; kvi_init(autocmd_list); char *pattern_filters[AUCMD_MAX_PATTERNS]; @@ -127,6 +126,8 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) }); } + int id = (HAS_KEY(opts, get_autocmds, id)) ? (int)opts->id : -1; + if (HAS_KEY(opts, get_autocmds, event)) { check_event = true; @@ -237,6 +238,10 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) continue; } + if (id != -1 && ac->id != id) { + continue; + } + // Skip autocmds from invalid groups if passed. if (group != 0 && ap->group != group) { continue; @@ -285,10 +290,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) PUT_C(autocmd_info, "desc", CSTR_AS_OBJ(ac->desc)); } - if (ac->exec.type == CALLABLE_CB) { + if (ac->handler_cmd) { + PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->handler_cmd)); + } else { PUT_C(autocmd_info, "command", STRING_OBJ(STRING_INIT)); - Callback *cb = &ac->exec.callable.cb; + Callback *cb = &ac->handler_fn; switch (cb->type) { case kCallbackLua: if (nlua_ref_is_function(cb->data.luaref)) { @@ -302,8 +309,6 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) case kCallbackNone: abort(); } - } else { - PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->exec.callable.cmd)); } PUT_C(autocmd_info, "pattern", CSTR_AS_OBJ(ap->pat)); @@ -317,23 +322,6 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err) PUT_C(autocmd_info, "buflocal", BOOLEAN_OBJ(false)); } - // TODO(sctx): It would be good to unify script_ctx to actually work with lua - // right now it's just super weird, and never really gives you the info that - // you would expect from this. - // - // I think we should be able to get the line number, filename, etc. from lua - // when we're executing something, and it should be easy to then save that - // info here. - // - // I think it's a big loss not getting line numbers of where options, autocmds, - // etc. are set (just getting "Sourced (lua)" or something is not that helpful. - // - // Once we do that, we can put these into the autocmd_info, but I don't think it's - // useful to do that at this time. - // - // PUT_C(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid)); - // PUT_C(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum)); - kvi_push(autocmd_list, DICT_OBJ(autocmd_info)); } } @@ -386,9 +374,9 @@ cleanup: /// - id: (number) autocommand id /// - event: (string) name of the triggered event |autocmd-events| /// - group: (number|nil) autocommand group id, if any -/// - match: (string) expanded value of [<amatch>] -/// - buf: (number) expanded value of [<abuf>] -/// - file: (string) expanded value of [<afile>] +/// - file: (string) [<afile>] (not expanded to a full path) +/// - match: (string) [<amatch>] (expanded to a full path) +/// - buf: (number) [<abuf>] /// - 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} @@ -406,8 +394,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc { int64_t autocmd_id = -1; char *desc = NULL; - AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; - Callback cb = CALLBACK_NONE; + char *handler_cmd = NULL; + Callback handler_fn = CALLBACK_NONE; Array event_array = unpack_string_or_array(event, "event", true, arena, err); if (ERROR_SET(err)) { @@ -432,13 +420,13 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; }); - cb.type = kCallbackLua; - cb.data.luaref = callback->data.luaref; + handler_fn.type = kCallbackLua; + handler_fn.data.luaref = callback->data.luaref; callback->data.luaref = LUA_NOREF; break; case kObjectTypeString: - cb.type = kCallbackFuncref; - cb.data.funcref = string_to_cstr(callback->data.string); + handler_fn.type = kCallbackFuncref; + handler_fn.data.funcref = string_to_cstr(callback->data.string); break; default: VALIDATE_EXP(false, "callback", "Lua function or Vim function name", @@ -446,12 +434,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc goto cleanup; }); } - - aucmd.type = CALLABLE_CB; - aucmd.callable.cb = cb; } else if (HAS_KEY(opts, create_autocmd, command)) { - aucmd.type = CALLABLE_EX; - aucmd.callable.cmd = string_to_cstr(opts->command); + handler_cmd = string_to_cstr(opts->command); } else { VALIDATE(false, "%s", "Required: 'command' or 'callback'", { goto cleanup; @@ -491,7 +475,6 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc int retval; FOREACH_ITEM(patterns, pat, { - // See: TODO(sctx) WITH_SCRIPT_CONTEXT(channel_id, { retval = autocmd_register(autocmd_id, event_nr, @@ -501,7 +484,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc opts->once, opts->nested, desc, - aucmd); + handler_cmd, + &handler_fn); }); if (retval == FAIL) { @@ -512,7 +496,11 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc }); cleanup: - aucmd_exec_free(&aucmd); + if (handler_cmd) { + XFREE_CLEAR(handler_cmd); + } else { + callback_free(&handler_fn); + } return autocmd_id; } @@ -631,7 +619,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou FUNC_API_SINCE(9) { char *augroup_name = name.data; - bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); + bool clear_autocmds = GET_BOOL_OR_TRUE(opts, create_augroup, clear); int augroup = -1; WITH_SCRIPT_CONTEXT(channel_id, { diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9480292d9a..aa349790b3 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -23,7 +23,6 @@ #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" #include "nvim/extmark.h" #include "nvim/extmark_defs.h" @@ -360,93 +359,91 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ memchrsub(lines[i], NUL, NL, l.size); } - try_start(); - - if (!MODIFIABLE(buf)) { - api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); - goto end; - } - - if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to save undo information"); - goto end; - } - - bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0); - - // If the size of the range is reducing (ie, new_len < old_len) we - // need to delete some old_len. We do this at the start, by - // repeatedly deleting line "start". - size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; - for (size_t i = 0; i < to_delete; i++) { - if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to delete line"); + TRY_WRAP(err, { + if (!MODIFIABLE(buf)) { + api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); goto end; } - } - if (to_delete > 0) { - extra -= (ptrdiff_t)to_delete; - } + if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to save undo information"); + goto end; + } - // For as long as possible, replace the existing old_len with the - // new old_len. This is a more efficient operation, as it requires - // less memory allocation and freeing. - size_t to_replace = old_len < new_len ? old_len : new_len; - bcount_t inserted_bytes = 0; - for (size_t i = 0; i < to_replace; i++) { - int64_t lnum = start + (int64_t)i; + bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0); - VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { - goto end; - }); + // If the size of the range is reducing (ie, new_len < old_len) we + // need to delete some old_len. We do this at the start, by + // repeatedly deleting line "start". + size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; + for (size_t i = 0; i < to_delete; i++) { + if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to delete line"); + goto end; + } + } - if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to replace line"); - goto end; + if (to_delete > 0) { + extra -= (ptrdiff_t)to_delete; } - inserted_bytes += (bcount_t)strlen(lines[i]) + 1; - } + // For as long as possible, replace the existing old_len with the + // new old_len. This is a more efficient operation, as it requires + // less memory allocation and freeing. + size_t to_replace = old_len < new_len ? old_len : new_len; + bcount_t inserted_bytes = 0; + for (size_t i = 0; i < to_replace; i++) { + int64_t lnum = start + (int64_t)i; + + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { + goto end; + }); + + if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to replace line"); + goto end; + } - // Now we may need to insert the remaining new old_len - for (size_t i = to_replace; i < new_len; i++) { - int64_t lnum = start + (int64_t)i - 1; + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; + } - VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { - goto end; - }); + // Now we may need to insert the remaining new old_len + for (size_t i = to_replace; i < new_len; i++) { + int64_t lnum = start + (int64_t)i - 1; - if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to insert line"); - goto end; - } + VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", { + goto end; + }); - inserted_bytes += (bcount_t)strlen(lines[i]) + 1; + if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to insert line"); + goto end; + } - extra++; - } + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; - // Adjust marks. Invalidate any which lie in the - // changed range, and move any in the remainder of the buffer. - linenr_T adjust = end > start ? MAXLNUM : 0; - mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra, - true, true, kExtmarkNOOP); + extra++; + } - extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0, - deleted_bytes, (int)new_len, 0, inserted_bytes, - kExtmarkUndo); + // Adjust marks. Invalidate any which lie in the + // changed range, and move any in the remainder of the buffer. + linenr_T adjust = end > start ? MAXLNUM : 0; + mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra, + true, true, kExtmarkNOOP); - changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); + extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0, + deleted_bytes, (int)new_len, 0, inserted_bytes, + kExtmarkUndo); - FOR_ALL_TAB_WINDOWS(tp, win) { - if (win->w_buffer == buf) { - fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra); - } - } + changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true); -end: - try_end(err); + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_buffer == buf) { + fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra); + } + } + end:; + }); } /// Sets (replaces) a range in the buffer @@ -593,101 +590,99 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In new_byte += (bcount_t)(last_item.size) + 1; } - try_start(); - - if (!MODIFIABLE(buf)) { - api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); - goto end; - } - - // Small note about undo states: unlike set_lines, we want to save the - // undo state of one past the end_row, since end_row is inclusive. - if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to save undo information"); - goto end; - } - - ptrdiff_t extra = 0; // lines added to text, can be negative - size_t old_len = (size_t)(end_row - start_row + 1); - - // If the size of the range is reducing (ie, new_len < old_len) we - // need to delete some old_len. We do this at the start, by - // repeatedly deleting line "start". - size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; - for (size_t i = 0; i < to_delete; i++) { - if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to delete line"); + TRY_WRAP(err, { + if (!MODIFIABLE(buf)) { + api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'"); goto end; } - } - if (to_delete > 0) { - extra -= (ptrdiff_t)to_delete; - } - - // For as long as possible, replace the existing old_len with the - // new old_len. This is a more efficient operation, as it requires - // less memory allocation and freeing. - size_t to_replace = old_len < new_len ? old_len : new_len; - for (size_t i = 0; i < to_replace; i++) { - int64_t lnum = start_row + (int64_t)i; - - VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { + // Small note about undo states: unlike set_lines, we want to save the + // undo state of one past the end_row, since end_row is inclusive. + if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to save undo information"); goto end; - }); + } - if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to replace line"); - goto end; + ptrdiff_t extra = 0; // lines added to text, can be negative + size_t old_len = (size_t)(end_row - start_row + 1); + + // If the size of the range is reducing (ie, new_len < old_len) we + // need to delete some old_len. We do this at the start, by + // repeatedly deleting line "start". + size_t to_delete = (new_len < old_len) ? old_len - new_len : 0; + for (size_t i = 0; i < to_delete; i++) { + if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to delete line"); + goto end; + } } - } - // Now we may need to insert the remaining new old_len - for (size_t i = to_replace; i < new_len; i++) { - int64_t lnum = start_row + (int64_t)i - 1; + if (to_delete > 0) { + extra -= (ptrdiff_t)to_delete; + } - VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { - goto end; - }); + // For as long as possible, replace the existing old_len with the + // new old_len. This is a more efficient operation, as it requires + // less memory allocation and freeing. + size_t to_replace = old_len < new_len ? old_len : new_len; + for (size_t i = 0; i < to_replace; i++) { + int64_t lnum = start_row + (int64_t)i; - if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { - api_set_error(err, kErrorTypeException, "Failed to insert line"); - goto end; + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { + goto end; + }); + + if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false, true) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to replace line"); + goto end; + } } - extra++; - } + // Now we may need to insert the remaining new old_len + for (size_t i = to_replace; i < new_len; i++) { + int64_t lnum = start_row + (int64_t)i - 1; - colnr_T col_extent = (colnr_T)(end_col - - ((end_row == start_row) ? start_col : 0)); - - // Adjust marks. Invalidate any which lie in the - // changed range, and move any in the remainder of the buffer. - // Do not adjust any cursors. need to use column-aware logic (below) - linenr_T adjust = end_row >= start_row ? MAXLNUM : 0; - mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra, - true, true, kExtmarkNOOP); - - extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col, - (int)(end_row - start_row), col_extent, old_byte, - (int)new_len - 1, (colnr_T)last_item.size, new_byte, - kExtmarkUndo); - - changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); - - FOR_ALL_TAB_WINDOWS(tp, win) { - if (win->w_buffer == buf) { - if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { - fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, - (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); - } else { - fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); + VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", { + goto end; + }); + + if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to insert line"); + goto end; } + + extra++; } - } -end: - try_end(err); + colnr_T col_extent = (colnr_T)(end_col + - ((end_row == start_row) ? start_col : 0)); + + // Adjust marks. Invalidate any which lie in the + // changed range, and move any in the remainder of the buffer. + // Do not adjust any cursors. need to use column-aware logic (below) + linenr_T adjust = end_row >= start_row ? MAXLNUM : 0; + mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra, + true, true, kExtmarkNOOP); + + extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col, + (int)(end_row - start_row), col_extent, old_byte, + (int)new_len - 1, (colnr_T)last_item.size, new_byte, + kExtmarkUndo); + + changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true); + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_buffer == buf) { + if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) { + fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row, + (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size); + } else { + fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra); + } + } + } + end:; + }); } /// Gets a range from the buffer. @@ -965,26 +960,27 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) return; } - 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; - } + int ren_ret = OK; + TRY_WRAP(err, { + 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); + // Using aucmd_*: autocommands will be executed by rename_buffer + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + ren_ret = rename_buffer(name.data); + aucmd_restbuf(&aco); - if (!is_curbuf) { - p_acd = save_acd; - } + if (!is_curbuf) { + p_acd = save_acd; + } + }); - if (try_end(err)) { + if (ERROR_SET(err)) { return; } @@ -1184,12 +1180,12 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Arena *arena, /// This temporarily switches current buffer to "buffer". /// If the current window already shows "buffer", the window is not switched. /// If a window inside the current tabpage (including a float) already shows the -/// buffer, then one of these windows will be set as current window temporarily. +/// buffer, then one of those windows will be set as current window temporarily. /// Otherwise a temporary scratch window (called the "autocmd window" for /// historical reasons) will be used. /// /// This is useful e.g. to call Vimscript functions that only work with the -/// current buffer/window currently, like |termopen()|. +/// current buffer/window currently, like `jobstart(…, {'term': v:true})`. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param fun Function to call inside the buffer (currently Lua callable @@ -1204,15 +1200,18 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) if (!buf) { return NIL; } - try_start(); - aco_save_T aco; - aucmd_prepbuf(&aco, buf); - Array args = ARRAY_DICT_INIT; - Object res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); + Object res = OBJECT_INIT; + TRY_WRAP(err, { + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + + Array args = ARRAY_DICT_INIT; + res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); + + aucmd_restbuf(&aco); + }); - aucmd_restbuf(&aco); - try_end(err); return res; } diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index ab57d5c009..23e08bd3fe 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -26,6 +26,7 @@ #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/ops.h" #include "nvim/pos_defs.h" #include "nvim/regexp.h" @@ -226,8 +227,8 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err addr = "?"; break; } - PUT_KEY(result, cmd, addr, CSTR_AS_OBJ(addr)); - PUT_KEY(result, cmd, nextcmd, CSTR_AS_OBJ(ea.nextcmd)); + PUT_KEY(result, cmd, addr, cstr_as_string(addr)); + PUT_KEY(result, cmd, nextcmd, cstr_as_string(ea.nextcmd)); // TODO(bfredl): nested keydict would be nice.. Dict mods = arena_dict(arena, 20); @@ -930,6 +931,9 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) gap = &ucmds; } else { buf_T *buf = find_buffer_by_handle(buffer, err); + if (ERROR_SET(err)) { + return; + } gap = &buf->b_ucmds; } diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index b38a7d4173..1d81b21be6 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,3 +1,5 @@ +// Island of misfit toys. + #include <stdbool.h> #include <stdint.h> #include <string.h> @@ -7,6 +9,7 @@ #include "nvim/api/extmark.h" #include "nvim/api/keysets_defs.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/validate.h" #include "nvim/api/vimscript.h" @@ -18,14 +21,14 @@ #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" +#include "nvim/marktree.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/message.h" #include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/pos_defs.h" +#include "nvim/strings.h" #include "nvim/types_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -82,6 +85,17 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) return buf->b_fnum; } +static uint32_t src2ns(Integer *src_id) +{ + if (*src_id == 0) { + *src_id = nvim_create_namespace((String)STRING_INIT); + } + if (*src_id < 0) { + return (((uint32_t)1) << 31) - 1; + } + return (uint32_t)(*src_id); +} + /// Clears highlights and virtual text from namespace and range of lines /// /// @deprecated use |nvim_buf_clear_namespace()|. @@ -100,6 +114,80 @@ void nvim_buf_clear_highlight(Buffer buffer, Integer ns_id, Integer line_start, nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err); } +/// Adds a highlight to buffer. +/// +/// @deprecated use |nvim_buf_set_extmark()| or |vim.hl.range()| +/// +/// Namespaces are used for batch deletion/updating of a set of highlights. To +/// create a namespace, use |nvim_create_namespace()| which returns a namespace +/// id. Pass it in to this function as `ns_id` to add highlights to the +/// namespace. All highlights in the same namespace can then be cleared with +/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be +/// deleted by an API call, pass `ns_id = -1`. +/// +/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the +/// highlight, the allocated id is then returned. If `hl_group` is the empty +/// string no highlight is added, but a new `ns_id` is still returned. This is +/// supported for backwards compatibility, new code should use +/// |nvim_create_namespace()| to create a new empty namespace. +/// +/// @param buffer Buffer handle, or 0 for current buffer +/// @param ns_id namespace to use or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line Line to highlight (zero-indexed) +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Error details, if any +/// @return The ns_id that was used +Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line, + Integer col_start, Integer col_end, Error *err) + FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(13) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { + return 0; + }); + VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { + return 0; + }); + + if (col_end < 0 || col_end > MAXCOL) { + col_end = MAXCOL; + } + + uint32_t ns = src2ns(&ns_id); + + if (!(line < buf->b_ml.ml_line_count)) { + // safety check, we can't add marks outside the range + return ns_id; + } + + int hl_id = 0; + if (hl_group.size > 0) { + hl_id = syn_check_group(hl_group.data, hl_group.size); + } else { + return ns_id; + } + + int end_line = (int)line; + if (col_end == MAXCOL) { + col_end = 0; + end_line++; + } + + DecorInline decor = DECOR_INLINE_INIT; + decor.data.hl.hl_id = hl_id; + + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, + decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); + return ns_id; +} /// Set the virtual text (annotation) for a buffer line. /// /// @deprecated use nvim_buf_set_extmark to use full virtual text functionality. @@ -636,23 +724,27 @@ void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object /// Gets the value of a global or local (buffer, window) option. /// /// @param[in] from Pointer to buffer or window for local option value. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param name The option name. /// @param[out] err Details of an error that may have occurred. /// /// @return the option value. -static Object get_option_from(void *from, OptScope req_scope, String name, Error *err) +static Object get_option_from(void *from, OptScope scope, String name, Error *err) { VALIDATE_S(name.size > 0, "option name", "<empty>", { return (Object)OBJECT_INIT; }); OptIndex opt_idx = find_option(name.data); + VALIDATE_S(opt_idx != kOptInvalid, "option name", name.data, { + return (Object)OBJECT_INIT; + }); + OptVal value = NIL_OPTVAL; - if (option_has_scope(opt_idx, req_scope)) { - value = get_option_value_for(opt_idx, req_scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, - req_scope, from, err); + if (option_has_scope(opt_idx, scope)) { + value = get_option_value_for(opt_idx, scope == kOptScopeGlobal ? OPT_GLOBAL : OPT_LOCAL, + scope, from, err); if (ERROR_SET(err)) { return (Object)OBJECT_INIT; } @@ -668,12 +760,12 @@ static Object get_option_from(void *from, OptScope req_scope, String name, Error /// Sets the value of a global or local (buffer, window) option. /// /// @param[in] to Pointer to buffer or window for local option value. -/// @param req_scope Requested option scope. See OptScope in option.h. +/// @param scope Option scope. See OptScope in option.h. /// @param name The option name. /// @param value New option value. /// @param[out] err Details of an error that may have occurred. -static void set_option_to(uint64_t channel_id, void *to, OptScope req_scope, String name, - Object value, Error *err) +static void set_option_to(uint64_t channel_id, void *to, OptScope scope, String name, Object value, + Error *err) { VALIDATE_S(name.size > 0, "option name", "<empty>", { return; @@ -698,12 +790,12 @@ static void set_option_to(uint64_t channel_id, void *to, OptScope req_scope, Str // For global-win-local options -> setlocal // For win-local options -> setglobal and setlocal (opt_flags == 0) const int opt_flags - = (req_scope == kOptScopeWin && !option_has_scope(opt_idx, kOptScopeGlobal)) + = (scope == kOptScopeWin && !option_has_scope(opt_idx, kOptScopeGlobal)) ? 0 - : ((req_scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); + : ((scope == kOptScopeGlobal) ? OPT_GLOBAL : OPT_LOCAL); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, opt_flags, req_scope, to, err); + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } @@ -798,7 +890,8 @@ theend: /// @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 +// XXX: c_grammar.lua is order-sensitive. + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } @@ -808,7 +901,105 @@ void nvim_subscribe(uint64_t channel_id, String event) /// @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 +// XXX: c_grammar.lua is order-sensitive. + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) FUNC_API_REMOTE_ONLY { // Does nothing. `rpcnotify(0,…)` broadcasts to all channels, there are no "subscriptions". } + +enum { LINE_BUFFER_MIN_SIZE = 4096, }; + +/// 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. +/// +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) +/// @param writeln Append a trailing newline +static void write_msg(String message, bool to_err, bool writeln) +{ + static StringBuilder out_line_buf = KV_INITIAL_VALUE; + static StringBuilder err_line_buf = KV_INITIAL_VALUE; + StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; + +#define PUSH_CHAR(c) \ + if (kv_max(*line_buf) == 0) { \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } \ + if (c == NL) { \ + kv_push(*line_buf, NUL); \ + if (to_err) { \ + emsg(line_buf->items); \ + } else { \ + msg(line_buf->items, 0); \ + } \ + if (msg_silent == 0) { \ + msg_didout = true; \ + } \ + kv_drop(*line_buf, kv_size(*line_buf)); \ + kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ + } else if (c == NUL) { \ + kv_push(*line_buf, NL); \ + } else { \ + kv_push(*line_buf, c); \ + } + + no_wait_return++; + for (uint32_t i = 0; i < message.size; i++) { + if (got_int) { + break; + } + PUSH_CHAR(message.data[i]); + } + if (writeln) { + PUSH_CHAR(NL); + } + no_wait_return--; + msg_end(); +} + +/// @deprecated +/// +/// @param str Message +void nvim_out_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, false, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_write(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, false); +} + +/// @deprecated +/// +/// @param str Message +void nvim_err_writeln(String str) + FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(13) +{ + write_msg(str, true, true); +} + +/// @deprecated +/// +/// Use `nvim_echo` or `nvim_exec_lua("vim.notify(...)", ...)` instead. +/// +/// @param msg Message to display to the user +/// @param log_level The log level +/// @param opts Reserved for future use. +/// @param[out] err Error details, if any +Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) + FUNC_API_SINCE(7) FUNC_API_DEPRECATED_SINCE(13) +{ + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, STRING_OBJ(msg)); + ADD_C(args, INTEGER_OBJ(log_level)); + ADD_C(args, DICT_OBJ(opts)); + + return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); +} diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index c94b8df9ea..8b31196eef 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -27,6 +27,7 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/move.h" #include "nvim/pos_defs.h" #include "nvim/sign.h" @@ -48,7 +49,7 @@ void api_extmark_free_all_mem(void) /// Creates a new namespace or gets an existing one. [namespace]() /// /// Namespaces are used for buffer highlights and virtual text, see -/// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. +/// |nvim_buf_set_extmark()|. /// /// Namespaces can be named or anonymous. If `name` matches an existing /// namespace, the associated id is returned. If `name` is an empty string @@ -61,7 +62,7 @@ Integer nvim_create_namespace(String name) { handle_T id = map_get(String, int)(&namespace_ids, name); if (id > 0) { - return id; + return (Integer)id; } id = next_namespace_id++; if (name.size > 0) { @@ -384,6 +385,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - hl_group : highlight group used for the text range. This and below /// highlight groups can be supplied either as a string or as an integer, /// the latter of which can be obtained using |nvim_get_hl_id_by_name()|. +/// +/// Multiple highlight groups can be stacked by passing an array (highest +/// priority last). /// - hl_eol : when true, for a multiline highlight covering the /// EOL of a line, continue the highlight for the rest /// of the screen line (just like for diff and @@ -396,6 +400,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// (highest priority last). /// - virt_text_pos : position of virtual text. Possible values: /// - "eol": right after eol character (default). +/// - "eol_right_align": display right aligned in the window +/// unless the virtual text is longer than +/// the space available. If the virtual +/// text is too long, it is truncated to +/// fit in the window after the EOL +/// character. If the line is wrapped, the +/// virtual text is shown after the end of +/// the line rather than the previous +/// screen line. /// - "overlay": display over the specified column, without /// shifting the underlying text. /// - "right_align": display right aligned in the window. @@ -498,6 +511,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT; char *url = NULL; bool has_hl = false; + bool has_hl_multiple = false; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -550,8 +564,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col2 = (int)val; } - hl.hl_id = (int)opts->hl_group; - has_hl = hl.hl_id > 0; + if (HAS_KEY(opts, set_extmark, hl_group)) { + if (opts->hl_group.type == kObjectTypeArray) { + Array arr = opts->hl_group.data.array; + if (arr.size >= 1) { + hl.hl_id = object_to_hl_id(arr.items[0], "hl_group item", err); + if (ERROR_SET(err)) { + goto error; + } + } + for (size_t i = 1; i < arr.size; i++) { + int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err); + if (ERROR_SET(err)) { + goto error; + } + if (hl_id) { + has_hl_multiple = true; + } + } + } else { + hl.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err); + if (ERROR_SET(err)) { + goto error; + } + } + has_hl = hl.hl_id > 0; + } + sign.hl_id = (int)opts->sign_hl_group; sign.cursorline_hl_id = (int)opts->cursorline_hl_group; sign.number_hl_id = (int)opts->number_hl_group; @@ -590,6 +629,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer virt_text.pos = kVPosOverlay; } else if (strequal("right_align", str.data)) { virt_text.pos = kVPosRightAlign; + } else if (strequal("eol_right_align", str.data)) { + virt_text.pos = kVPosEndOfLineRightAlign; } else if (strequal("inline", str.data)) { virt_text.pos = kVPosInline; } else { @@ -793,6 +834,21 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } } + if (has_hl_multiple) { + Array arr = opts->hl_group.data.array; + for (size_t i = arr.size - 1; i > 0; i--) { // skip hl_group[0], handled as hl.hl_id below + int hl_id = object_to_hl_id(arr.items[i], "hl_group item", err); + if (hl_id > 0) { + DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT; + sh.hl_id = hl_id; + sh.flags = opts->hl_eol ? kSHHlEol : 0; + sh.next = decor_indexed; + decor_indexed = decor_put_sh(sh); + decor_flags |= MT_FLAG_DECOR_HL; + } + } + } + DecorInline decor = DECOR_INLINE_INIT; if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL || schar_high(hl.conceal_char)) { @@ -856,95 +912,6 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id); } -uint32_t src2ns(Integer *src_id) -{ - if (*src_id == 0) { - *src_id = nvim_create_namespace((String)STRING_INIT); - } - if (*src_id < 0) { - return (((uint32_t)1) << 31) - 1; - } - return (uint32_t)(*src_id); -} - -/// Adds a highlight to buffer. -/// -/// Useful for plugins that dynamically generate highlights to a buffer -/// (like a semantic highlighter or linter). The function adds a single -/// highlight to a buffer. Unlike |matchaddpos()| highlights follow changes to -/// line numbering (as lines are inserted/removed above the highlighted line), -/// like signs and marks do. -/// -/// Namespaces are used for batch deletion/updating of a set of highlights. To -/// create a namespace, use |nvim_create_namespace()| which returns a namespace -/// id. Pass it in to this function as `ns_id` to add highlights to the -/// namespace. All highlights in the same namespace can then be cleared with -/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be -/// deleted by an API call, pass `ns_id = -1`. -/// -/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the -/// highlight, the allocated id is then returned. If `hl_group` is the empty -/// string no highlight is added, but a new `ns_id` is still returned. This is -/// supported for backwards compatibility, new code should use -/// |nvim_create_namespace()| to create a new empty namespace. -/// -/// @param buffer Buffer handle, or 0 for current buffer -/// @param ns_id namespace to use or -1 for ungrouped highlight -/// @param hl_group Name of the highlight group to use -/// @param line Line to highlight (zero-indexed) -/// @param col_start Start of (byte-indexed) column range to highlight -/// @param col_end End of (byte-indexed) column range to highlight, -/// or -1 to highlight to end of line -/// @param[out] err Error details, if any -/// @return The ns_id that was used -Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, Integer line, - Integer col_start, Integer col_end, Error *err) - FUNC_API_SINCE(1) -{ - buf_T *buf = find_buffer_by_handle(buffer, err); - if (!buf) { - return 0; - } - - VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", { - return 0; - }); - VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", { - return 0; - }); - - if (col_end < 0 || col_end > MAXCOL) { - col_end = MAXCOL; - } - - uint32_t ns = src2ns(&ns_id); - - if (!(line < buf->b_ml.ml_line_count)) { - // safety check, we can't add marks outside the range - return ns_id; - } - - int hl_id = 0; - if (hl_group.size > 0) { - hl_id = syn_check_group(hl_group.data, hl_group.size); - } else { - return ns_id; - } - - int end_line = (int)line; - if (col_end == MAXCOL) { - col_end = 0; - end_line++; - } - - DecorInline decor = DECOR_INLINE_INIT; - decor.data.hl.hl_id = hl_id; - - extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL); - return ns_id; -} - /// Clears |namespace|d objects (highlights, |extmarks|, virtual text) from /// a region. /// @@ -1012,8 +979,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// ``` /// ["start", tick] /// ``` -/// - on_buf: called for each buffer being redrawn (before -/// window callbacks) +/// - on_buf: called for each buffer being redrawn (once per edit, +/// before window callbacks) /// ``` /// ["buf", bufnr, tick] /// ``` diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h index af2d51c95c..0b4d84110b 100644 --- a/src/nvim/api/extmark.h +++ b/src/nvim/api/extmark.h @@ -1,5 +1,6 @@ #pragma once +#include <stdbool.h> #include <stdint.h> // IWYU pragma: keep #include "nvim/api/keysets_defs.h" // IWYU pragma: keep diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 96aabb851f..6625908cda 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -8,18 +8,19 @@ typedef struct { typedef struct { OptionalKeys is_set__context_; - Array types; + ArrayOf(String) types; } Dict(context); typedef struct { OptionalKeys is_set__set_decoration_provider_; - LuaRef on_start; - LuaRef on_buf; - LuaRef on_win; - LuaRef on_line; - LuaRef on_end; - LuaRef _on_hl_def; - LuaRef _on_spell_nav; + LuaRefOf(("start" _, Integer tick), *Boolean) on_start; + LuaRefOf(("buf" _, Integer bufnr, Integer tick)) on_buf; + LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow), + *Boolean) on_win; + LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row), *Boolean) on_line; + LuaRefOf(("end" _, Integer tick)) on_end; + LuaRefOf(("hl_def" _)) _on_hl_def; + LuaRefOf(("spell_nav" _)) _on_spell_nav; } Dict(set_decoration_provider); typedef struct { @@ -28,7 +29,7 @@ typedef struct { Integer end_line; Integer end_row; Integer end_col; - HLGroupID hl_group; + Object hl_group; Array virt_text; String virt_text_pos; Integer virt_text_win_col; @@ -116,7 +117,7 @@ typedef struct { String relative; String split; Window win; - Array bufpos; + ArrayOf(Integer) bufpos; Boolean external; Boolean focusable; Boolean mouse; @@ -172,17 +173,17 @@ typedef struct { Boolean altfont; Boolean nocombine; Boolean default_ DictKey(default); - Object cterm; - Object foreground; - Object fg; - Object background; - Object bg; - Object ctermfg; - Object ctermbg; - Object special; - Object sp; - Object link; - Object global_link; + Union(Integer, String) cterm; + Union(Integer, String) foreground; + Union(Integer, String) fg; + Union(Integer, String) background; + Union(Integer, String) bg; + Union(Integer, String) ctermfg; + Union(Integer, String) ctermbg; + Union(Integer, String) special; + Union(Integer, String) sp; + HLGroupID link; + HLGroupID global_link; Boolean fallback; Integer blend; Boolean fg_indexed; @@ -230,9 +231,9 @@ typedef struct { typedef struct { OptionalKeys is_set__clear_autocmds_; Buffer buffer; - Object event; - Object group; - Object pattern; + Union(String, ArrayOf(String)) event; + Union(Integer, String) group; + Union(String, ArrayOf(String)) pattern; } Dict(clear_autocmds); typedef struct { @@ -241,31 +242,33 @@ typedef struct { Object callback; String command; String desc; - Object group; + Union(Integer, String) group; Boolean nested; Boolean once; - Object pattern; + Union(String, ArrayOf(String)) pattern; } Dict(create_autocmd); typedef struct { OptionalKeys is_set__exec_autocmds_; Buffer buffer; - Object group; + Union(Integer, String) group; Boolean modeline; - Object pattern; + Union(String, ArrayOf(String)) pattern; Object data; } Dict(exec_autocmds); typedef struct { OptionalKeys is_set__get_autocmds_; - Object event; - Object group; - Object pattern; - Object buffer; + Union(String, ArrayOf(String)) event; + Union(Integer, String) group; + Union(String, ArrayOf(String)) pattern; + Union(Integer, ArrayOf(Integer)) buffer; + Integer id; } Dict(get_autocmds); typedef struct { - Object clear; + OptionalKeys is_set__create_augroup_; + Boolean clear; } Dict(create_augroup); typedef struct { @@ -275,12 +278,12 @@ typedef struct { Integer count; String reg; Boolean bang; - Array args; + ArrayOf(String) args; Dict magic; Dict mods; - Object nargs; - Object addr; - Object nextcmd; + Union(Integer, String) nargs; + String addr; + String nextcmd; } Dict(cmd); typedef struct { @@ -324,6 +327,7 @@ typedef struct { } Dict(cmd_opts); typedef struct { + Boolean err; Boolean verbose; } Dict(echo_opts); @@ -333,11 +337,30 @@ typedef struct { typedef struct { OptionalKeys is_set__buf_attach_; - LuaRef on_lines; - LuaRef on_bytes; - LuaRef on_changedtick; - LuaRef on_detach; - LuaRef on_reload; + LuaRefOf(("lines" _, + Integer bufnr, + Integer changedtick, + Integer first, + Integer last_old, + Integer last_new, + Integer byte_count, + Integer *deleted_codepoints, + Integer *deleted_codeunits), *Boolean) on_lines; + LuaRefOf(("bytes" _, + Integer bufnr, + Integer changedtick, + Integer start_row, + Integer start_col, + Integer start_byte, + Integer old_end_row, + Integer old_end_col, + Integer old_end_byte, + Integer new_end_row, + Integer new_end_col, + Integer new_end_byte), *Boolean) on_bytes; + LuaRefOf(("changedtick" _, Integer bufnr, Integer changedtick)) on_changedtick; + LuaRefOf(("detach" _, Integer bufnr)) on_detach; + LuaRefOf(("reload" _, Integer bufnr)) on_reload; Boolean utf_sizes; Boolean preview; } Dict(buf_attach); @@ -350,7 +373,7 @@ typedef struct { typedef struct { OptionalKeys is_set__open_term_; - LuaRef on_input; + LuaRefOf(("input" _, Integer term, Integer bufnr, any data)) on_input; Boolean force_crlf; } Dict(open_term); @@ -361,12 +384,13 @@ typedef struct { typedef struct { OptionalKeys is_set__xdl_diff_; - LuaRef on_hunk; + LuaRefOf((Integer start_a, Integer count_a, Integer start_b, Integer count_b), + *Integer) on_hunk; String result_type; String algorithm; Integer ctxlen; Integer interhunkctxlen; - Object linematch; + Union(Boolean, Integer) linematch; Boolean ignore_whitespace; Boolean ignore_whitespace_change; Boolean ignore_whitespace_change_at_eol; diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 3289daeb6f..64f8a35d54 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -14,6 +14,7 @@ #include "nvim/buffer_defs.h" #include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/option.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" @@ -23,15 +24,15 @@ #endif static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *opt_idxp, - int *scope, OptScope *req_scope, void **from, char **filetype, + int *opt_flags, OptScope *scope, void **from, char **filetype, Error *err) { #define HAS_KEY_X(d, v) HAS_KEY(d, option, v) if (HAS_KEY_X(opts, scope)) { if (!strcmp(opts->scope.data, "local")) { - *scope = OPT_LOCAL; + *opt_flags = OPT_LOCAL; } else if (!strcmp(opts->scope.data, "global")) { - *scope = OPT_GLOBAL; + *opt_flags = OPT_GLOBAL; } else { VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, { return FAIL; @@ -39,14 +40,14 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } } - *req_scope = kOptScopeGlobal; + *scope = kOptScopeGlobal; if (filetype != NULL && HAS_KEY_X(opts, filetype)) { *filetype = opts->filetype.data; } if (HAS_KEY_X(opts, win)) { - *req_scope = kOptScopeWin; + *scope = kOptScopeWin; *from = find_window_by_handle(opts->win, err); if (ERROR_SET(err)) { return FAIL; @@ -54,12 +55,12 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } if (HAS_KEY_X(opts, buf)) { - VALIDATE(!(HAS_KEY_X(opts, scope) && *scope == OPT_GLOBAL), "%s", + VALIDATE(!(HAS_KEY_X(opts, scope) && *opt_flags == OPT_GLOBAL), "%s", "cannot use both global 'scope' and 'buf'", { return FAIL; }); - *scope = OPT_LOCAL; - *req_scope = kOptScopeBuf; + *opt_flags = OPT_LOCAL; + *scope = kOptScopeBuf; *from = find_buffer_by_handle(opts->buf, err); if (ERROR_SET(err)) { return FAIL; @@ -81,10 +82,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * if (*opt_idxp == kOptInvalid) { // unknown option api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name); - } else if (*req_scope == kOptScopeBuf || *req_scope == kOptScopeWin) { + } else if (*scope == kOptScopeBuf || *scope == kOptScopeWin) { // if 'buf' or 'win' is passed, make sure the option supports it - if (!option_has_scope(*opt_idxp, *req_scope)) { - char *tgt = *req_scope == kOptScopeBuf ? "buf" : "win"; + if (!option_has_scope(*opt_idxp, *scope)) { + char *tgt = *scope == kOptScopeBuf ? "buf" : "win"; char *global = option_has_scope(*opt_idxp, kOptScopeGlobal) ? "global " : ""; char *req = option_has_scope(*opt_idxp, kOptScopeBuf) ? "buffer-local " @@ -95,7 +96,7 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex * } } - return OK; + return ERROR_SET(err) ? FAIL : OK; #undef HAS_KEY_X } @@ -151,13 +152,13 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) FUNC_API_SINCE(9) FUNC_API_RET_ALLOC { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, &filetype, - err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, + &filetype, err)) { return (Object)OBJECT_INIT; } @@ -181,7 +182,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) from = ftbuf; } - OptVal value = get_option_value_for(opt_idx, scope, req_scope, from, err); + OptVal value = get_option_value_for(opt_idx, opt_flags, scope, from, err); if (ftbuf != NULL) { // restore curwin/curbuf and a few other things @@ -224,10 +225,11 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( FUNC_API_SINCE(9) { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *to = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &to, NULL, err)) { + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &to, NULL, + err)) { return; } @@ -237,9 +239,9 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( // - option is global or local to window (global-local) // // Then force scope to local since we don't want to change the global option - if (req_scope == kOptScopeWin && scope == 0) { + if (scope == kOptScopeWin && opt_flags == 0) { if (option_has_scope(opt_idx, kOptScopeGlobal)) { - scope = OPT_LOCAL; + opt_flags = OPT_LOCAL; } } @@ -255,7 +257,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( }); WITH_SCRIPT_CONTEXT(channel_id, { - set_option_value_for(name.data, opt_idx, optval, scope, req_scope, to, err); + set_option_value_for(name.data, opt_idx, optval, opt_flags, scope, to, err); }); } @@ -310,16 +312,16 @@ Dict nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, Error FUNC_API_SINCE(11) { OptIndex opt_idx = 0; - int scope = 0; - OptScope req_scope = kOptScopeGlobal; + int opt_flags = 0; + OptScope scope = kOptScopeGlobal; void *from = NULL; - if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, NULL, + if (!validate_option_value_args(opts, name.data, &opt_idx, &opt_flags, &scope, &from, NULL, err)) { return (Dict)ARRAY_DICT_INIT; } - buf_T *buf = (req_scope == kOptScopeBuf) ? (buf_T *)from : curbuf; - win_T *win = (req_scope == kOptScopeWin) ? (win_T *)from : curwin; + buf_T *buf = (scope == kOptScopeBuf) ? (buf_T *)from : curbuf; + win_T *win = (scope == kOptScopeWin) ? (win_T *)from : curwin; - return get_vimoption(name, scope, buf, win, arena, err); + return get_vimoption(name, opt_flags, buf, win, arena, err); } diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 59e7373f68..5f9d20ee73 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -1,4 +1,5 @@ #include <assert.h> +#include <lauxlib.h> #include <stdbool.h> #include <stddef.h> #include <stdint.h> @@ -7,7 +8,6 @@ #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/ascii_defs.h" #include "nvim/assert_defs.h" #include "nvim/eval/decode.h" #include "nvim/eval/typval.h" @@ -15,6 +15,7 @@ #include "nvim/eval/userfunc.h" #include "nvim/lua/executor.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/types_defs.h" #include "nvim/vim_defs.h" diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 26d5ac09a8..6dee86dcf5 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -21,6 +21,8 @@ # define Dict(name) KeyDict_##name # define DictHash(name) KeyDict_##name##_get_field # define DictKey(name) +# define LuaRefOf(...) LuaRef +# define Union(...) Object # include "api/private/defs.h.inline.generated.h" #endif diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8ddaecc58e..c98635f8fd 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -31,19 +31,18 @@ #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/pos_defs.h" #include "nvim/types_defs.h" -#include "nvim/ui.h" -#include "nvim/ui_defs.h" -#include "nvim/version.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/api_metadata.generated.h" -# include "api/private/helpers.c.generated.h" +# include "api/private/helpers.c.generated.h" // IWYU pragma: keep #endif /// Start block that may cause Vimscript exceptions while evaluating another code /// -/// Used when caller is supposed to be operating when other Vimscript code is being -/// processed and that “other Vimscript code” must not be affected. +/// Used just in case caller is supposed to be operating when other Vimscript code +/// is being processed and that “other Vimscript code” must not be affected. +/// +/// @warning Avoid calling directly; use TRY_WRAP instead. /// /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) @@ -55,74 +54,33 @@ void try_enter(TryState *const tstate) .current_exception = current_exception, .msg_list = (const msglist_T *const *)msg_list, .private_msg_list = NULL, - .trylevel = trylevel, .got_int = got_int, .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). msg_list = &tstate->private_msg_list; current_exception = NULL; - trylevel = 1; got_int = false; did_throw = false; need_rethrow = false; did_emsg = false; -} - -/// End try block, set the error message if any and restore previous state -/// -/// @warning Return is consistent with most functions (false on error), not with -/// try_end (true on error). -/// -/// @param[in] tstate Previous state to restore. -/// @param[out] err Location where error should be saved. -/// -/// @return false if error occurred, true otherwise. -bool try_leave(const TryState *const tstate, Error *const err) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - const bool ret = !try_end(err); - assert(trylevel == 0); - assert(!need_rethrow); - assert(!got_int); - assert(!did_throw); - assert(!did_emsg); - assert(msg_list == &tstate->private_msg_list); - assert(*msg_list == NULL); - assert(current_exception == NULL); - msg_list = (msglist_T **)tstate->msg_list; - current_exception = tstate->current_exception; - trylevel = tstate->trylevel; - got_int = tstate->got_int; - did_throw = tstate->did_throw; - need_rethrow = tstate->need_rethrow; - did_emsg = tstate->did_emsg; - return ret; -} - -/// Start block that may cause vimscript exceptions -/// -/// Each try_start() call should be mirrored by try_end() call. -/// -/// To be used as a replacement of `:try … catch … endtry` in C code, in cases -/// when error flag could not already be set. If there may be pending error -/// state at the time try_start() is executed which needs to be preserved, -/// try_enter()/try_leave() pair should be used instead. -void try_start(void) -{ trylevel++; } -/// End try block, set the error message if any and return true if an error -/// occurred. +/// Ends a `try_enter` block; sets error message if any. /// -/// @param err Pointer to the stack-allocated error object -/// @return true if an error occurred -bool try_end(Error *err) +/// @warning Avoid calling directly; use TRY_WRAP instead. +/// +/// @param[out] err Pointer to the stack-allocated error object +void try_leave(const TryState *const tstate, Error *const err) + FUNC_ATTR_NONNULL_ALL { // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. + assert(trylevel > 0); trylevel--; // Set by emsg(), affects aborting(). See also enter_cleanup(). @@ -165,7 +123,20 @@ bool try_end(Error *err) discard_current_exception(); } - return ERROR_SET(err); + assert(msg_list == &tstate->private_msg_list); + assert(*msg_list == NULL); + assert(current_exception == NULL); + assert(!got_int); + assert(!did_throw); + assert(!need_rethrow); + assert(!did_emsg); + // Restore the exception context. + msg_list = (msglist_T **)tstate->msg_list; + current_exception = tstate->current_exception; + got_int = tstate->got_int; + did_throw = tstate->did_throw; + need_rethrow = tstate->need_rethrow; + did_emsg = tstate->did_emsg; } /// Recursively expands a vimscript value in a dict @@ -805,7 +776,7 @@ char *api_typename(ObjectType t) UNREACHABLE; } -HlMessage parse_hl_msg(Array chunks, Error *err) +HlMessage parse_hl_msg(Array chunks, bool is_err, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; for (size_t i = 0; i < chunks.size; i++) { @@ -820,7 +791,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String str = copy_string(chunk.items[0].data.string, NULL); - int hl_id = 0; + int hl_id = is_err ? HLF_E : 0; if (chunk.size == 2) { hl_id = object_to_hl_id(chunk.items[1], "text highlight", err); } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index d06f5c9c65..d581c6bc10 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -1,7 +1,7 @@ #pragma once #include <stdbool.h> -#include <stddef.h> +#include <stddef.h> // IWYU pragma: keep #include "klib/kvec.h" #include "nvim/api/private/defs.h" // IWYU pragma: keep @@ -147,27 +147,19 @@ typedef struct { except_T *current_exception; msglist_T *private_msg_list; const msglist_T *const *msg_list; - int trylevel; int got_int; bool did_throw; int need_rethrow; int did_emsg; } TryState; -// `msg_list` controls the collection of abort-causing non-exception errors, -// which would otherwise be ignored. This pattern is from do_cmdline(). -// // TODO(bfredl): prepare error-handling at "top level" (nv_event). #define TRY_WRAP(err, code) \ do { \ - msglist_T **saved_msg_list = msg_list; \ - msglist_T *private_msg_list; \ - msg_list = &private_msg_list; \ - private_msg_list = NULL; \ - try_start(); \ + TryState tstate; \ + try_enter(&tstate); \ code; \ - try_end(err); \ - msg_list = saved_msg_list; /* Restore the exception context. */ \ + try_leave(&tstate, err); \ } while (0) // Execute code with cursor position saved and restored and textlock active. diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 56a3f1cf23..dce47cd99f 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -7,11 +7,12 @@ #include "nvim/api/vim.h" #include "nvim/buffer_defs.h" #include "nvim/globals.h" -#include "nvim/memory.h" +#include "nvim/memory_defs.h" +#include "nvim/types_defs.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "api/tabpage.c.generated.h" +# include "api/tabpage.c.generated.h" // IWYU pragma: keep #endif /// Gets the windows in a tabpage @@ -146,11 +147,9 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err) } if (tp == curtab) { - try_start(); - win_goto(wp); - if (!try_end(err) && curwin != wp) { - api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win); - } + TRY_WRAP(err, { + win_goto(wp); + }); } else if (tp->tp_curwin != wp) { tp->tp_prevwin = tp->tp_curwin; tp->tp_curwin = wp; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index b09a9ed253..f6f32a73ed 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -23,7 +23,6 @@ #include "nvim/event/wstream.h" #include "nvim/globals.h" #include "nvim/grid.h" -#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/macros_defs.h" #include "nvim/main.h" @@ -34,6 +33,7 @@ #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/packer.h" +#include "nvim/msgpack_rpc/packer_defs.h" #include "nvim/option.h" #include "nvim/types_defs.h" #include "nvim/ui.h" @@ -537,7 +537,7 @@ static void prepare_call(RemoteUI *ui, const char *name) ui_alloc_buf(ui); } - // To optimize data transfer(especially for "grid_line"), we bundle adjacent + // To optimize data transfer (especially for "grid_line"), we bundle adjacent // calls to same method together, so only add a new call entry if the last // method call is different from "name" diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h index cdccc27ba4..3f996ec219 100644 --- a/src/nvim/api/ui.h +++ b/src/nvim/api/ui.h @@ -4,6 +4,7 @@ #include "nvim/api/private/defs.h" // IWYU pragma: keep #include "nvim/highlight_defs.h" // IWYU pragma: keep +#include "nvim/macros_defs.h" #include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/ui_defs.h" // IWYU pragma: keep diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 0ed208fc1a..74718e7ac5 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -136,13 +136,13 @@ void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array bu FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent, - Integer level) + Integer level, Integer hl_id) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_pos(Integer pos, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_special_char(String c, Boolean shift, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void cmdline_hide(Integer level) +void cmdline_hide(Integer level, Boolean abort) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_block_show(Array lines) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; @@ -158,7 +158,7 @@ void wildmenu_select(Integer selected) void wildmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void msg_show(String kind, Array content, Boolean replace_last) +void msg_show(String kind, Array content, Boolean replace_last, Boolean history) FUNC_API_SINCE(6) FUNC_API_FAST FUNC_API_REMOTE_ONLY; void msg_clear(void) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 83f9aa573d..c103a56032 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -20,6 +20,7 @@ #include "nvim/api/vim.h" #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/channel.h" @@ -27,8 +28,8 @@ #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/decoration.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/errors.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -44,6 +45,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/log.h" #include "nvim/lua/executor.h" @@ -76,7 +78,6 @@ #include "nvim/runtime.h" #include "nvim/sign_defs.h" #include "nvim/state.h" -#include "nvim/state_defs.h" #include "nvim/statusline.h" #include "nvim/statusline_defs.h" #include "nvim/strings.h" @@ -86,8 +87,6 @@ #include "nvim/vim_defs.h" #include "nvim/window.h" -#define LINE_BUFFER_MIN_SIZE 4096 - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.c.generated.h" #endif @@ -518,26 +517,6 @@ Object nvim_exec_lua(String code, Array args, Arena *arena, Error *err) return nlua_exec(code, args, kRetObject, arena, err); } -/// Notify the user with a message -/// -/// Relays the call to vim.notify . By default forwards your message in the -/// echo area but can be overridden to trigger desktop notifications. -/// -/// @param msg Message to display to the user -/// @param log_level The log level -/// @param opts Reserved for future use. -/// @param[out] err Error details, if any -Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err) - FUNC_API_SINCE(7) -{ - MAXSIZE_TEMP_ARRAY(args, 3); - ADD_C(args, STRING_OBJ(msg)); - ADD_C(args, INTEGER_OBJ(log_level)); - ADD_C(args, DICT_OBJ(opts)); - - return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err); -} - /// Calculates the number of display cells occupied by `text`. /// Control characters including [<Tab>] count as one cell. /// @@ -594,11 +573,10 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Arena *arena, Er kvi_init(cookie.rv); int flags = DIP_DIRFILE | (all ? DIP_ALL : 0); - TryState tstate; - try_enter(&tstate); - do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); - vim_ignored = try_leave(&tstate, err); + TRY_WRAP(err, { + do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &cookie); + }); return arena_take_arraybuilder(arena, &cookie.rv); } @@ -668,16 +646,9 @@ void nvim_set_current_dir(String dir, Error *err) memcpy(string, dir.data, dir.size); string[dir.size] = NUL; - try_start(); - - if (!changedir_func(string, kCdScopeGlobal)) { - if (!try_end(err)) { - api_set_error(err, kErrorTypeException, "Failed to change directory"); - } - return; - } - - try_end(err); + TRY_WRAP(err, { + changedir_func(string, kCdScopeGlobal); + }); } /// Gets the current line. @@ -776,20 +747,24 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, NULL, err); } -/// Echo a message. +/// Prints a message given by a list of `[text, hl_group]` "chunks". /// -/// @param chunks A list of `[text, hl_group]` arrays, each representing a -/// text chunk with specified highlight group name or ID. -/// `hl_group` element can be omitted for no highlight. +/// Example: +/// ```lua +/// vim.api.nvim_echo({ { 'chunk1-line1\nchunk1-line2\n' }, { 'chunk2-line1' } }, true, {}) +/// ``` +/// +/// @param chunks List of `[text, hl_group]` pairs, where each is a `text` string highlighted by +/// the (optional) name or ID `hl_group`. /// @param history if true, add to |message-history|. /// @param opts Optional parameters. -/// - verbose: Message is printed as a result of 'verbose' option. -/// If Nvim was invoked with -V3log_file, the message will be -/// redirected to the log_file and suppressed from direct output. +/// - err: Treat the message like `:echoerr`. Sets `hl_group` to |hl-ErrorMsg| by default. +/// - verbose: Message is controlled by the 'verbose' option. Nvim invoked with `-V3log` +/// will write the message to the "log" file instead of standard output. void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { - HlMessage hl_msg = parse_hl_msg(chunks, err); + HlMessage hl_msg = parse_hl_msg(chunks, opts->err, err); if (ERROR_SET(err)) { goto error; } @@ -798,7 +773,8 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) verbose_enter(); } - msg_multihl(hl_msg, history ? "echomsg" : "echo", history); + char *kind = opts->verbose ? NULL : opts->err ? "echoerr" : history ? "echomsg" : "echo"; + msg_multihl(hl_msg, kind, history, opts->err); if (opts->verbose) { verbose_leave(); @@ -814,37 +790,6 @@ error: hl_msg_free(hl_msg); } -/// Writes a message to the Vim output buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_out_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, false, false); -} - -/// Writes a message to the Vim error buffer. Does not append "\n", the -/// message is buffered (won't display) until a linefeed is written. -/// -/// @param str Message -void nvim_err_write(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, false); -} - -/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is -/// flushed (and displayed). -/// -/// @param str Message -/// @see nvim_err_write() -void nvim_err_writeln(String str) - FUNC_API_SINCE(1) -{ - write_msg(str, true, true); -} - /// Gets the current list of buffer handles /// /// Includes unlisted (unloaded/deleted) buffers, like `:ls!`. @@ -892,19 +837,9 @@ 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) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to buffer %d", - buffer); - } + TRY_WRAP(err, { + do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); + }); } /// Gets the current list of window handles. @@ -951,14 +886,9 @@ void nvim_set_current_win(Window window, Error *err) return; } - try_start(); - goto_tabpage_win(win_find_tabpage(win), win); - if (!try_end(err) && win != curwin) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to window %d", - window); - } + TRY_WRAP(err, { + goto_tabpage_win(win_find_tabpage(win), win); + }); } /// Creates a new, empty, unnamed buffer. @@ -973,74 +903,76 @@ void nvim_set_current_win(Window window, Error *err) 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)); - if (buf == NULL) { - unblock_autocmds(); - goto fail; - } + Buffer ret = 0; - // 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 (ml_open(buf) == FAIL) { - unblock_autocmds(); - goto fail; - } - - // Set last_changedtick to avoid triggering a TextChanged autocommand right - // after it was added. - buf->b_last_changedtick = buf_get_changedtick(buf); - buf->b_last_changedtick_i = buf_get_changedtick(buf); - buf->b_last_changedtick_pum = buf_get_changedtick(buf); + TRY_WRAP(err, { + // 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)); + if (buf == NULL) { + unblock_autocmds(); + goto fail; + } - // Only strictly needed for scratch, but could just as well be consistent - // 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); + // 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 (ml_open(buf) == FAIL) { + unblock_autocmds(); + goto fail; + } - if (scratch) { - set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, - kOptScopeBuf, buf); - set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, - kOptScopeBuf, 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; - } + // Set last_changedtick to avoid triggering a TextChanged autocommand right + // after it was added. + buf->b_last_changedtick = buf_get_changedtick(buf); + buf->b_last_changedtick_i = buf_get_changedtick(buf); + 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 later first happens + // to reach a window or aucmd_prepbuf() .. + buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + + if (scratch) { + set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL, 0, + kOptScopeBuf, buf); + set_option_direct_for(kOptBuftype, STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL, 0, + kOptScopeBuf, buf); + assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already + buf->b_p_swf = false; + buf->b_p_ml = false; + } - unblock_autocmds(); + 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; - } + 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; + ret = buf->b_fnum; + fail:; + }); -fail: - if (!try_end(err)) { + if (ret == 0 && !ERROR_SET(err)) { api_set_error(err, kErrorTypeException, "Failed to create buffer"); } - return 0; + return ret; } /// Open a terminal instance in a buffer /// /// By default (and currently the only option) the terminal will not be -/// connected to an external process. Instead, input send on the channel +/// connected to an external process. Instead, input sent on the channel /// will be echoed directly by the terminal. This is useful to display /// ANSI terminal sequences returned as part of a rpc message, or similar. /// @@ -1051,6 +983,19 @@ fail: /// Then |nvim_chan_send()| can be called immediately to process sequences /// in a virtual terminal having the intended size. /// +/// Example: this `TermHl` command can be used to display and highlight raw ANSI termcodes, so you +/// can use Nvim as a "scrollback pager" (for terminals like kitty): [ansi-colorize]() +/// [terminal-scrollback-pager]() +/// +/// ```lua +/// vim.api.nvim_create_user_command('TermHl', function() +/// local b = vim.api.nvim_create_buf(false, true) +/// local chan = vim.api.nvim_open_term(b, {}) +/// vim.api.nvim_chan_send(chan, table.concat(vim.api.nvim_buf_get_lines(0, 0, -1, false), '\n')) +/// vim.api.nvim_win_set_buf(0, b) +/// end, { desc = 'Highlights ANSI termcodes in curbuf' }) +/// ``` +/// /// @param buffer the buffer to use (expected to be empty) /// @param opts Optional parameters. /// - on_input: Lua callback for input sent, i e keypresses in terminal @@ -1205,14 +1150,9 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) return; } - try_start(); - goto_tabpage_tp(tp, true, true); - if (!try_end(err) && tp != curtab) { - api_set_error(err, - kErrorTypeException, - "Failed to switch to tabpage %d", - tabpage); - } + TRY_WRAP(err, { + goto_tabpage_tp(tp, true, true); + }); } /// Pastes at cursor (in any mode), and sets "redo" so dot (|.|) will repeat the input. UIs call @@ -1545,20 +1485,17 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena) return rv; } -/// Self-identifies the client. +/// Self-identifies the client. Sets the `client` object returned by |nvim_get_chan_info()|. /// -/// The client/plugin/application should call this after connecting, to provide -/// hints about its identity and purpose, for debugging and orchestration. +/// Clients should call this just after connecting, to provide hints for debugging and +/// orchestration. (Note: Something is better than nothing! Fields are optional, but at least set +/// `name`.) /// -/// Can be called more than once; the caller should merge old info if -/// appropriate. Example: library first identifies the channel, then a plugin -/// using that library later identifies itself. -/// -/// @note "Something is better than nothing". You don't need to include all the -/// fields. +/// Can be called more than once; the caller should merge old info if appropriate. Example: library +/// first identifies the channel, then a plugin using that library later identifies itself. /// /// @param channel_id -/// @param name Short name for the connected client +/// @param name Client short-name. Sets the `client.name` field of |nvim_get_chan_info()|. /// @param version Dict describing the version, with these /// (optional) keys: /// - "major" major version (defaults to 0 if not set, for no release yet) @@ -1632,6 +1569,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// Gets information about a channel. /// +/// See |nvim_list_uis()| for an example of how to get channel info. +/// /// @param chan channel_id, or 0 for current channel /// @returns Channel info dict with these keys: /// - "id" Channel id. @@ -1649,8 +1588,8 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String /// "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. /// for conpty on Windows). /// - "buffer" (optional) Buffer connected to |terminal| instance. -/// - "client" (optional) Info about the peer (client on the other end of the RPC channel), -/// which it provided via |nvim_set_client_info()|. +/// - "client" (optional) Info about the peer (client on the other end of the channel), as set +/// by |nvim_set_client_info()|. /// Dict nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err) FUNC_API_SINCE(4) @@ -1676,55 +1615,6 @@ Array nvim_list_chans(Arena *arena) return channel_all_info(arena); } -/// 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. -/// -/// @param message Message to write -/// @param to_err true: message is an error (uses `emsg` instead of `msg`) -/// @param writeln Append a trailing newline -static void write_msg(String message, bool to_err, bool writeln) -{ - static StringBuilder out_line_buf = KV_INITIAL_VALUE; - static StringBuilder err_line_buf = KV_INITIAL_VALUE; - StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf; - -#define PUSH_CHAR(c) \ - if (kv_max(*line_buf) == 0) { \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } \ - if (c == NL) { \ - kv_push(*line_buf, NUL); \ - if (to_err) { \ - emsg(line_buf->items); \ - } else { \ - msg(line_buf->items, 0); \ - } \ - if (msg_silent == 0) { \ - msg_didout = true; \ - } \ - kv_drop(*line_buf, kv_size(*line_buf)); \ - kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \ - } else if (c == NUL) { \ - kv_push(*line_buf, NL); \ - } else { \ - kv_push(*line_buf, c); \ - } - - no_wait_return++; - for (uint32_t i = 0; i < message.size; i++) { - if (got_int) { - break; - } - PUSH_CHAR(message.data[i]); - } - if (writeln) { - PUSH_CHAR(NL); - } - no_wait_return--; - msg_end(); -} - // Functions used for testing purposes /// Returns object given as argument. @@ -1796,6 +1686,14 @@ Dict nvim__stats(Arena *arena) /// Gets a list of dictionaries representing attached UIs. /// +/// Example: The Nvim builtin |TUI| sets its channel info as described in |startup-tui|. In +/// particular, it sets `client.name` to "nvim-tui". So you can check if the TUI is running by +/// inspecting the client name of each UI: +/// +/// ```lua +/// vim.print(vim.api.nvim_get_chan_info(vim.api.nvim_list_uis()[1].chan).client.name) +/// ``` +/// /// @return Array of UI dictionaries, each with these keys: /// - "height" Requested height of the UI /// - "width" Requested width of the UI @@ -2086,7 +1984,9 @@ Array nvim_get_mark(String name, Dict(empty) *opts, Arena *arena, Error *err) /// the "highlights" key in {opts} is true. Each element of the array is a /// |Dict| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. -/// - group: (string) Name of highlight group. +/// - group: (string) Name of highlight group. May be removed in the future, use +/// `groups` instead. +/// - groups: (array) Names of stacked highlight groups (highest priority last). Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err) FUNC_API_SINCE(8) FUNC_API_FAST { @@ -2138,6 +2038,7 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, }); int stc_hl_id = 0; + int scl_hl_id = 0; statuscol_T statuscol = { 0 }; SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 }; @@ -2146,23 +2047,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, int cul_id = 0; int num_id = 0; linenr_T lnum = statuscol_lnum; + foldinfo_T cursorline_fi = { 0 }; decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id); statuscol.sattrs = sattrs; statuscol.foldinfo = fold_info(wp, lnum); - wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0; + win_update_cursorline(wp, &cursorline_fi); + statuscol.sign_cul_id = use_cursor_line_highlight(wp, lnum) ? cul_id : 0; + scl_hl_id = use_cursor_line_highlight(wp, lnum) ? HLF_CLS : HLF_SC; - if (wp->w_p_cul) { - if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) { - wp->w_cursorline = statuscol.foldinfo.fi_lnum; - } - statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR); - } - - statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0; if (num_id) { stc_hl_id = num_id; - } else if (statuscol.use_cul) { + } else if (use_cursor_line_highlight(wp, lnum)) { stc_hl_id = HLF_CLN; } else if (wp->w_p_rnu) { stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB); @@ -2215,22 +2111,19 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list + const char *dfltname = get_default_stl_hl(opts->use_tabline ? NULL : wp, + opts->use_winbar, stc_hl_id); if (hltab->start == NULL || (hltab->start - buf) != 0) { - Dict hl_info = arena_dict(arena, 2); - const char *grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, - opts->use_winbar, stc_hl_id); - + Dict hl_info = arena_dict(arena, 3); PUT_C(hl_info, "start", INTEGER_OBJ(0)); - PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname)); - + PUT_C(hl_info, "group", CSTR_AS_OBJ(dfltname)); + Array groups = arena_array(arena, 1); + ADD_C(groups, CSTR_AS_OBJ(dfltname)); + PUT_C(hl_info, "groups", ARRAY_OBJ(groups)); ADD_C(hl_values, DICT_OBJ(hl_info)); } for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) { - Dict hl_info = arena_dict(arena, 2); - - PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf)); - const char *grpname; if (sp->userhl == 0) { grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id); @@ -2240,7 +2133,18 @@ Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); grpname = arena_memdupz(arena, user_group, strlen(user_group)); } + + const char *combine = sp->item == STL_SIGNCOL ? syn_id2name(scl_hl_id) + : sp->item == STL_FOLDCOL ? grpname : dfltname; + Dict hl_info = arena_dict(arena, 3); + PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf)); PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname)); + Array groups = arena_array(arena, 1 + (combine != grpname)); + if (combine != grpname) { + ADD_C(groups, CSTR_AS_OBJ(combine)); + } + ADD_C(groups, CSTR_AS_OBJ(grpname)); + PUT_C(hl_info, "groups", ARRAY_OBJ(groups)); ADD_C(hl_values, DICT_OBJ(hl_info)); } PUT_C(result, "highlights", ARRAY_OBJ(hl_values)); @@ -2270,9 +2174,13 @@ void nvim_error_event(uint64_t channel_id, Integer lvl, String data) /// @return Dict containing these keys: /// - winid: (number) floating window id /// - bufnr: (number) buffer id in floating window -Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena) +Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena, Error *err) { Dict rv = arena_dict(arena, 2); + if ((get_cot_flags() & kOptCotFlagPopup) == 0) { + api_set_error(err, kErrorTypeException, "completeopt option does not include popup"); + return rv; + } if (HAS_KEY(opts, complete_set, info)) { win_T *wp = pum_set_info((int)index, opts->info.data); if (wp) { @@ -2389,13 +2297,23 @@ void nvim__redraw(Dict(redraw) *opts, Error *err) "%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; + int64_t begin_raw = kv_A(opts->range, 0).data.integer; + int64_t end_raw = kv_A(opts->range, 1).data.integer; + buf_T *rbuf = win ? win->w_buffer : (buf ? buf : curbuf); - if (last == -1) { - last = rbuf->b_ml.ml_line_count; + linenr_T line_count = rbuf->b_ml.ml_line_count; + + int begin = (int)MIN(begin_raw, line_count); + int end; + if (end_raw == -1) { + end = line_count; + } else { + end = (int)MIN(MAX(begin, end_raw), line_count); + } + + if (begin < end) { + redraw_buf_range_later(rbuf, 1 + begin, end); } - redraw_buf_range_later(rbuf, first, last); } // Redraw later types require update_screen() so call implicitly unless set to false. diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 165cc93fbe..fc7e7e1a06 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -2,6 +2,7 @@ #include <stdbool.h> #include <stddef.h> #include <stdint.h> +#include <stdlib.h> #include <string.h> #include "klib/kvec.h" @@ -21,6 +22,7 @@ #include "nvim/garray_defs.h" #include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/runtime.h" #include "nvim/vim_defs.h" #include "nvim/viml/parser/expressions.h" @@ -78,24 +80,24 @@ String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error * capture_ga = &capture_local; } - try_start(); - if (opts->output) { - msg_silent++; - msg_col = 0; // prevent leading spaces - } + TRY_WRAP(err, { + if (opts->output) { + msg_silent++; + msg_col = 0; // prevent leading spaces + } - const sctx_T save_current_sctx = api_set_sctx(channel_id); + const sctx_T save_current_sctx = api_set_sctx(channel_id); - do_source_str(src.data, "nvim_exec2()"); - if (opts->output) { - capture_ga = save_capture_ga; - msg_silent = save_msg_silent; - // Put msg_col back where it was, since nothing should have been written. - msg_col = save_msg_col; - } + do_source_str(src.data, "nvim_exec2()"); + if (opts->output) { + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + // Put msg_col back where it was, since nothing should have been written. + msg_col = save_msg_col; + } - current_sctx = save_current_sctx; - try_end(err); + current_sctx = save_current_sctx; + }); if (ERROR_SET(err)) { goto theend; @@ -125,19 +127,17 @@ theend: /// /// On execution error: fails with Vimscript error, updates v:errmsg. /// -/// Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate multiple lines of Vim script -/// or an Ex command directly, use |nvim_exec2()|. To construct an Ex command using a structured -/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use -/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|. +/// Prefer |nvim_cmd()| or |nvim_exec2()| instead. To modify an Ex command in a structured way +/// before executing it, modify the result of |nvim_parse_cmd()| then pass it to |nvim_cmd()|. /// /// @param command Ex command string /// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - try_start(); - do_cmdline_cmd(command.data); - try_end(err); + TRY_WRAP(err, { + do_cmdline_cmd(command.data); + }); } /// Evaluates a Vimscript |expression|. Dicts and Lists are recursively expanded. @@ -231,10 +231,9 @@ static Object _call_function(String fn, Array args, dict_T *self, Arena *arena, funcexe.fe_selfdict = self; TRY_WRAP(err, { - // call_func() retval is deceptive, ignore it. Instead we set `msg_list` - // (see above) to capture abort-causing non-exception errors. - call_func(fn.data, (int)fn.size, &rettv, (int)args.size, - vim_args, &funcexe); + // call_func() retval is deceptive, ignore it. Instead TRY_WRAP sets `msg_list` to capture + // abort-causing non-exception errors. + (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, vim_args, &funcexe); }); if (!ERROR_SET(err)) { @@ -282,20 +281,23 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena, typval_T rettv; bool mustfree = false; switch (dict.type) { - case kObjectTypeString: - try_start(); - if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) { - api_set_error(err, kErrorTypeException, - "Failed to evaluate dict expression"); - } - clear_evalarg(&EVALARG_EVALUATE, NULL); - if (try_end(err)) { + case kObjectTypeString: { + int eval_ret; + TRY_WRAP(err, { + eval_ret = eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE); + clear_evalarg(&EVALARG_EVALUATE, NULL); + }); + if (ERROR_SET(err)) { return rv; } + if (eval_ret != OK) { + abort(); // Should not happen. + } // Evaluation of the string arg created a new dict or increased the // refcount of a dict. Not necessary for a RPC dict. mustfree = true; break; + } case kObjectTypeDict: object_to_vim(dict, &rettv, err); break; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 6f5a9a90c0..1132452faf 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1,3 +1,4 @@ +#include <assert.h> #include <stdbool.h> #include <string.h> @@ -7,25 +8,22 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/tabpage.h" #include "nvim/api/win_config.h" #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" #include "nvim/drawscreen.h" #include "nvim/errors.h" #include "nvim/eval/window.h" -#include "nvim/extmark_defs.h" #include "nvim/globals.h" -#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" #include "nvim/macros_defs.h" #include "nvim/mbyte.h" #include "nvim/memory.h" +#include "nvim/memory_defs.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/pos_defs.h" @@ -33,7 +31,6 @@ #include "nvim/syntax.h" #include "nvim/types_defs.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/ui_defs.h" #include "nvim/vim_defs.h" #include "nvim/window.h" @@ -104,10 +101,12 @@ /// @param config Map defining the window configuration. Keys: /// - relative: Sets the window layout to "floating", placed at (row,col) /// coordinates relative to: -/// - "editor" The global editor grid -/// - "win" Window given by the `win` field, or current window. -/// - "cursor" Cursor position in current window. -/// - "mouse" Mouse position +/// - "cursor" Cursor position in current window. +/// - "editor" The global editor grid. +/// - "laststatus" 'laststatus' if present, or last row. +/// - "mouse" Mouse position. +/// - "tabline" Tabline if present, or first row. +/// - "win" Window given by the `win` field, or current window. /// - win: |window-ID| window to split, or relative window when creating a /// float (relative="win"). /// - anchor: Decides which corner of the float to place at (row,col): @@ -702,7 +701,9 @@ Dict(win_config) nvim_win_get_config(Window window, Arena *arena, Error *err) FUNC_API_SINCE(6) { /// Keep in sync with FloatRelative in buffer_defs.h - static const char *const float_relative_str[] = { "editor", "win", "cursor", "mouse" }; + static const char *const float_relative_str[] = { + "editor", "win", "cursor", "mouse", "tabline", "laststatus" + }; /// Keep in sync with WinSplit in buffer_defs.h static const char *const win_split_str[] = { "left", "right", "above", "below" }; @@ -808,6 +809,10 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeCursor; } else if (striequal(str, "mouse")) { *out = kFloatRelativeMouse; + } else if (striequal(str, "tabline")) { + *out = kFloatRelativeTabline; + } else if (striequal(str, "laststatus")) { + *out = kFloatRelativeLaststatus; } else { return false; } @@ -890,7 +895,7 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, *is_present = true; } -static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type, +static bool parse_bordertext_pos(win_T *wp, String bordertext_pos, BorderTextType bordertext_type, WinConfig *fconfig, Error *err) { AlignTextPos *align; @@ -904,7 +909,9 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex } if (bordertext_pos.size == 0) { - *align = kAlignLeft; + if (!wp) { + *align = kAlignLeft; + } return true; } @@ -1245,7 +1252,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco } // handles unset 'title_pos' same as empty string - if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) { + if (!parse_bordertext_pos(wp, config->title_pos, kBorderTextTitle, fconfig, err)) { goto fail; } } else { @@ -1272,7 +1279,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco } // handles unset 'footer_pos' same as empty string - if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) { + if (!parse_bordertext_pos(wp, config->footer_pos, kBorderTextFooter, fconfig, err)) { goto fail; } } else { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 5a4972ef23..d968af421d 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -1,4 +1,3 @@ -#include <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -28,7 +27,7 @@ #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "api/window.c.generated.h" +# include "api/window.c.generated.h" // IWYU pragma: keep #endif /// Gets the current buffer in a window @@ -63,11 +62,6 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) 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; @@ -187,14 +181,9 @@ void nvim_win_set_height(Window window, Integer height, Error *err) return; } - if (height > INT_MAX || height < INT_MIN) { - api_set_error(err, kErrorTypeValidation, "Height value outside range"); - return; - } - - try_start(); - win_setheight_win((int)height, win); - try_end(err); + TRY_WRAP(err, { + win_setheight_win((int)height, win); + }); } /// Gets the window width @@ -229,14 +218,9 @@ void nvim_win_set_width(Window window, Integer width, Error *err) return; } - if (width > INT_MAX || width < INT_MIN) { - api_set_error(err, kErrorTypeValidation, "Width value outside range"); - return; - } - - try_start(); - win_setwidth_win((int)width, win); - try_end(err); + TRY_WRAP(err, { + win_setwidth_win((int)width, win); + }); } /// Gets a window-scoped (w:) variable @@ -383,19 +367,16 @@ void nvim_win_hide(Window window, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - TryState tstate; - try_enter(&tstate); - - // Never close the autocommand window. - if (is_aucmd_win(win)) { - emsg(_(e_autocmd_close)); - } else if (tabpage == curtab) { - win_close(win, false, false); - } else { - win_close_othertab(win, false, tabpage); - } - - vim_ignored = try_leave(&tstate, err); + TRY_WRAP(err, { + // Never close the autocommand window. + if (is_aucmd_win(win)) { + emsg(_(e_autocmd_close)); + } else if (tabpage == curtab) { + win_close(win, false, false); + } else { + win_close_othertab(win, false, tabpage); + } + }); } /// Closes the window (like |:close| with a |window-ID|). @@ -415,10 +396,9 @@ void nvim_win_close(Window window, Boolean force, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - TryState tstate; - try_enter(&tstate); - ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); - vim_ignored = try_leave(&tstate, err); + TRY_WRAP(err, { + ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); + }); } /// Calls a function with window as temporary current window. @@ -441,15 +421,15 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - try_start(); Object res = OBJECT_INIT; - win_execute_T win_execute_args; - if (win_execute_before(&win_execute_args, win, tabpage)) { - Array args = ARRAY_DICT_INIT; - res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); - } - win_execute_after(&win_execute_args); - try_end(err); + TRY_WRAP(err, { + win_execute_T win_execute_args; + if (win_execute_before(&win_execute_args, win, tabpage)) { + Array args = ARRAY_DICT_INIT; + res = nlua_call_ref(fun, NULL, args, kRetLuaref, NULL, err); + } + win_execute_after(&win_execute_args); + }); return res; } @@ -476,6 +456,7 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) } win->w_ns_hl = (NS)ns_id; + win->w_ns_hl_winhl = -1; win->w_hl_needs_update = true; redraw_later(win, UPD_NOT_VALID); } |