diff options
Diffstat (limited to 'src/nvim/api')
-rw-r--r-- | src/nvim/api/autocmd.c | 126 | ||||
-rw-r--r-- | src/nvim/api/buffer.c | 171 | ||||
-rw-r--r-- | src/nvim/api/buffer.h | 3 | ||||
-rw-r--r-- | src/nvim/api/command.c | 186 | ||||
-rw-r--r-- | src/nvim/api/deprecated.c | 12 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 139 | ||||
-rw-r--r-- | src/nvim/api/extmark.h | 3 | ||||
-rw-r--r-- | src/nvim/api/keysets.lua | 105 | ||||
-rw-r--r-- | src/nvim/api/options.c | 30 | ||||
-rw-r--r-- | src/nvim/api/private/converter.c | 18 | ||||
-rw-r--r-- | src/nvim/api/private/defs.h | 6 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.c | 29 | ||||
-rw-r--r-- | src/nvim/api/private/dispatch.h | 17 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 150 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 9 | ||||
-rw-r--r-- | src/nvim/api/tabpage.c | 16 | ||||
-rw-r--r-- | src/nvim/api/ui.c | 229 | ||||
-rw-r--r-- | src/nvim/api/ui.h | 2 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 67 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 259 | ||||
-rw-r--r-- | src/nvim/api/vimscript.c | 33 | ||||
-rw-r--r-- | src/nvim/api/win_config.c | 158 | ||||
-rw-r--r-- | src/nvim/api/window.c | 42 |
23 files changed, 1068 insertions, 742 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 79ae7994f7..931363e199 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -1,8 +1,12 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <stdbool.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include "lauxlib.h" #include "nvim/api/autocmd.h" @@ -12,7 +16,12 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/autocmd.c.generated.h" @@ -38,7 +47,7 @@ static int64_t next_autocmd_id = 1; /// Get all autocommands that match the corresponding {opts}. /// /// These examples will get autocommands matching ALL the given criteria: -/// <pre> +/// <pre>lua /// -- Matches all criteria /// autocommands = vim.api.nvim_get_autocmds({ /// group = "MyGroup", @@ -59,6 +68,9 @@ static int64_t next_autocmd_id = 1; /// - 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 +/// |autocmd-buflocal|. Cannot be used with {pattern} /// @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). @@ -240,7 +252,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) assert(pattern_filters[i]); char *pat = pattern_filters[i]; - int patlen = (int)STRLEN(pat); + int patlen = (int)strlen(pat); if (aupat_is_buflocal(pat, patlen)) { aupat_normalize_buflocal_pat(pattern_buflocal, @@ -349,82 +361,49 @@ cleanup: return autocmd_list; } -/// Create an |autocommand| -/// -/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand -/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands). -/// -/// Example using callback: -/// <pre> -/// -- Lua function -/// local myluafun = function() print("This buffer enters") end -/// -/// -- Vimscript function name (as a string) -/// local myvimfun = "g:MyVimFunction" -/// -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// callback = myluafun, -- Or myvimfun -/// }) -/// </pre> -/// -/// Lua functions receive a table with information about the autocmd event as an argument. To use -/// a function which itself accepts another (optional) parameter, wrap the function -/// in a lambda: -/// -/// <pre> -/// -- Lua function with an optional parameter. -/// -- The autocmd callback would pass a table as argument but this -/// -- function expects number|nil -/// local myluafun = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() end +/// Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript +/// function _name_ string) or `command` (Ex command string). /// +/// Example using Lua callback: +/// <pre>lua /// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { /// pattern = {"*.c", "*.h"}, -/// callback = function() myluafun() end, +/// callback = function(ev) +/// print(string.format('event fired: %s', vim.inspect(ev))) +/// end /// }) /// </pre> /// -/// Example using command: -/// <pre> +/// Example using an Ex command as the handler: +/// <pre>lua /// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { /// pattern = {"*.c", "*.h"}, /// command = "echo 'Entering a C or C++ file'", /// }) /// </pre> /// -/// Example values for pattern: -/// <pre> -/// pattern = "*.py" -/// pattern = { "*.py", "*.pyi" } -/// </pre> -/// -/// Example values for event: -/// <pre> -/// "BufWritePre" -/// {"CursorHold", "BufWritePre", "BufWritePost"} +/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME" +/// and "~" must be expanded explicitly: +/// <pre>lua +/// pattern = vim.fn.expand("~") .. "/some/path/*.py" /// </pre> /// -/// @param event (string|array) The event or events to register this autocommand -/// @param opts Dictionary of autocommand options: -/// - group (string|integer) optional: the autocommand group name or -/// id to match against. -/// - pattern (string|array) optional: pattern or patterns to match -/// against |autocmd-pattern|. -/// - buffer (integer) optional: buffer number for buffer local autocommands +/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`). +/// @param opts Options dict: +/// - group (string|integer) optional: autocommand group name or id to match against. +/// - pattern (string|array) optional: pattern(s) to match literally |autocmd-pattern|. +/// - buffer (integer) optional: buffer number for buffer-local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern}. -/// - desc (string) optional: description of the autocommand. -/// - callback (function|string) optional: if a string, the name of a Vimscript function -/// to call when this autocommand is triggered. Otherwise, a Lua function which is -/// called when this autocommand is triggered. Cannot be used with {command}. Lua -/// callbacks can return true to delete the autocommand; in addition, they accept a -/// single table argument with the following keys: -/// - id: (number) the autocommand id -/// - event: (string) the name of the event that triggered the autocommand -/// |autocmd-events| -/// - group: (number|nil) the autocommand group id, if it exists -/// - match: (string) the expanded value of |<amatch>| -/// - buf: (number) the expanded value of |<abuf>| -/// - file: (string) the expanded value of |<afile>| +/// - desc (string) optional: description (for documentation and troubleshooting). +/// - callback (function|string) optional: Lua function (or Vimscript function name, if +/// string) called when the event(s) is triggered. Lua callback can return true to +/// delete the autocommand, and receives a table argument with these keys: +/// - id: (number) autocommand id +/// - event: (string) name of the triggered event |autocmd-events| +/// - group: (number|nil) autocommand group id, if any +/// - match: (string) expanded value of |<amatch>| +/// - buf: (number) expanded value of |<abuf>| +/// - file: (string) expanded value of |<afile>| /// - data: (any) arbitrary data passed to |nvim_exec_autocmds()| /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} @@ -433,7 +412,7 @@ cleanup: /// - nested (boolean) optional: defaults to false. Run nested /// autocommands |autocmd-nested|. /// -/// @return Integer id of the created autocommand. +/// @return Autocommand id (number) /// @see |autocommand| /// @see |nvim_del_autocmd()| Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, @@ -454,8 +433,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc } if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "cannot pass both: 'callback' and 'command' for the same autocmd"); + api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both"); goto cleanup; } else if (opts->callback.type != kObjectTypeNil) { // TODO(tjdevries): It's possible we could accept callable tables, @@ -596,7 +574,7 @@ void nvim_del_autocmd(Integer id, Error *err) } /// Clear all autocommands that match the corresponding {opts}. To delete -/// a particular autocmd, see |nvim_del_autocmd|. +/// a particular autocmd, see |nvim_del_autocmd()|. /// @param opts Parameters /// - event: (string|table) /// Examples: @@ -684,7 +662,7 @@ cleanup: /// Create or get an autocommand group |autocmd-groups|. /// /// To get an existing group id, do: -/// <pre> +/// <pre>lua /// local id = vim.api.nvim_create_augroup("MyGroup", { /// clear = false /// }) @@ -725,7 +703,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou /// /// To get a group id one can use |nvim_get_autocmds()|. /// -/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in /// this group will also be deleted and cleared. This group will no longer exist. /// @param id Integer The id of the group. /// @see |nvim_del_augroup_by_name()| @@ -743,10 +721,10 @@ void nvim_del_augroup_by_id(Integer id, Error *err) /// Delete an autocommand group by name. /// -/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in /// this group will also be deleted and cleared. This group will no longer exist. /// @param name String The name of the group. -/// @see |autocommand-groups| +/// @see |autocmd-groups| void nvim_del_augroup_by_name(String name, Error *err) FUNC_API_SINCE(9) { @@ -887,12 +865,12 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { - ADD(*array, copy_object(*v)); + ADD(*array, copy_object(*v, NULL)); } else if (v->type == kObjectTypeArray) { if (!check_autocmd_string_array(v->data.array, k, err)) { return false; } - *array = copy_array(v->data.array); + *array = copy_array(v->data.array, NULL); } else { if (required) { api_set_error(err, @@ -962,7 +940,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob patlen = aucmd_pattern_length(pat); } } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(*patterns, "pattern", err)) { + if (!check_autocmd_string_array(v->data.array, "pattern", err)) { return false; } diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 5e90e40dd3..fe9e6077d6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -3,25 +3,30 @@ // Some of this code was adapted from 'if_py_both.h' from the original // vim source + +#include <assert.h> #include <lauxlib.h> -#include <limits.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> -#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "lua.h" #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/decoration.h" #include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" #include "nvim/extmark.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/mapping.h" #include "nvim/mark.h" @@ -29,9 +34,10 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/ops.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" @@ -78,7 +84,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// /// Example (Lua): capture buffer updates in a global `events` variable /// (use "print(vim.inspect(events))" to see its contents): -/// <pre> +/// <pre>lua /// events = {} /// vim.api.nvim_buf_attach(0, false, { /// on_lines=function(...) table.insert(events, {...}) end}) @@ -271,6 +277,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Integer start, Integer end, Boolean strict_indexing, + lua_State *lstate, Error *err) FUNC_API_SINCE(1) { @@ -300,21 +307,18 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, return rv; } - rv.size = (size_t)(end - start); - rv.items = xcalloc(rv.size, sizeof(Object)); + size_t size = (size_t)(end - start); + + init_line_array(lstate, &rv, size); - if (!buf_collect_lines(buf, rv.size, start, - (channel_id != VIML_INTERNAL_CALL), &rv, err)) { + if (!buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv, + lstate, err)) { goto end; } end: if (ERROR_SET(err)) { - for (size_t i = 0; i < rv.size; i++) { - xfree(rv.items[i].data.string.data); - } - - xfree(rv.items); + api_free_array(rv); rv.items = NULL; } @@ -355,6 +359,8 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err) /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// +/// @see |nvim_buf_set_text()| +/// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index @@ -527,6 +533,8 @@ end: /// /// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines. /// +/// @see |nvim_buf_set_lines()| +/// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start_row First line index @@ -570,7 +578,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In char *str_at_end = NULL; // Another call to ml_get_buf() may free the line, so make a copy. - str_at_start = xstrdup((char *)ml_get_buf(buf, (linenr_T)start_row, false)); + str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false)); size_t len_at_start = strlen(str_at_start); if (start_col < 0 || (size_t)start_col > len_at_start) { api_set_error(err, kErrorTypeValidation, "start_col out of bounds"); @@ -578,7 +586,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In } // Another call to ml_get_buf() may free the line, so make a copy. - str_at_end = xstrdup((char *)ml_get_buf(buf, (linenr_T)end_row, false)); + str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false)); size_t len_at_end = strlen(str_at_end); if (end_col < 0 || (size_t)end_col > len_at_end) { api_set_error(err, kErrorTypeValidation, "end_col out of bounds"); @@ -608,7 +616,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - const char *bufline = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false); old_byte += (bcount_t)(strlen(bufline)) + 1; } old_byte += (bcount_t)end_col + 1; @@ -786,7 +794,8 @@ early_end: ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col, Integer end_row, Integer end_col, - Dictionary opts, Error *err) + Dictionary opts, lua_State *lstate, + Error *err) FUNC_API_SINCE(9) { Array rv = ARRAY_DICT_INIT; @@ -826,33 +835,37 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + size_t size = (size_t)(end_row - start_row) + 1; + + init_line_array(lstate, &rv, size); + if (start_row == end_row) { - String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + String line = buf_get_text(buf, start_row, start_col, end_col, err); if (ERROR_SET(err)) { - return rv; + goto end; } - - ADD(rv, STRING_OBJ(line)); + push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl); return rv; } - rv.size = (size_t)(end_row - start_row) + 1; - rv.items = xcalloc(rv.size, sizeof(Object)); + String str = buf_get_text(buf, start_row, start_col, MAXCOL - 1, err); + + push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl); - rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL - 1, replace_nl, err)); if (ERROR_SET(err)) { goto end; } - if (rv.size > 2) { - Array tmp = ARRAY_DICT_INIT; - tmp.items = &rv.items[1]; - if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) { + if (size > 2) { + if (!buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate, + err)) { goto end; } } - rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + str = buf_get_text(buf, end_row, 0, end_col, err); + push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl); + if (ERROR_SET(err)) { goto end; } @@ -945,7 +958,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// @param[out] err Error details, if any /// @returns Array of |maparg()|-like dictionaries describing mappings. /// The "buffer" key holds the associated buffer handle. -ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err) +ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) FUNC_API_SINCE(3) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -954,7 +967,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, Stri return (Array)ARRAY_DICT_INIT; } - return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL); + return keymap_array(mode, buf); } /// Sets a buffer-local |mapping| for the given mode. @@ -1021,7 +1034,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Buffer name -String nvim_buf_get_name(Buffer buffer, Error *err) +String nvim_buf_get_name(Buffer buffer, Arena *arena, Error *err) FUNC_API_SINCE(1) { String rv = STRING_INIT; @@ -1031,7 +1044,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err) return rv; } - return cstr_to_string((char *)buf->b_ffname); + return cstr_as_string(buf->b_ffname); } /// Sets the full file name for a buffer @@ -1386,3 +1399,91 @@ static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bo index++; return index; } + +/// Initialise a string array either: +/// - on the Lua stack (as a table) (if lstate is not NULL) +/// - as an API array object (if lstate is NULL). +/// +/// @param lstate Lua state. When NULL the Array is initialized instead. +/// @param a Array to initialize +/// @param size Size of array +static inline void init_line_array(lua_State *lstate, Array *a, size_t size) +{ + if (lstate) { + lua_createtable(lstate, (int)size, 0); + } else { + a->size = size; + a->items = xcalloc(a->size, sizeof(Object)); + } +} + +/// Push a string onto either the Lua stack (as a table element) or an API array object. +/// +/// For Lua, a table of the correct size must be created first. +/// API array objects must be pre allocated. +/// +/// @param lstate Lua state. When NULL the Array is pushed to instead. +/// @param a Array to push onto when not using Lua +/// @param s String to push +/// @param len Size of string +/// @param idx 0-based index to place s +/// @param replace_nl Replace newlines ('\n') with null ('\0') +static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx, + bool replace_nl) +{ + if (lstate) { + // Vim represents NULs as NLs + if (s && replace_nl && strchr(s, '\n')) { + char *tmp = xmemdupz(s, len); + strchrsub(tmp, '\n', '\0'); + lua_pushlstring(lstate, tmp, len); + xfree(tmp); + } else { + lua_pushlstring(lstate, s, len); + } + lua_rawseti(lstate, -2, idx + 1); + } else { + String str = STRING_INIT; + if (s) { + str = cbuf_to_string(s, len); + if (replace_nl) { + // Vim represents NULs as NLs, but this may confuse clients. + strchrsub(str.data, '\n', '\0'); + } + } + + a->items[idx] = STRING_OBJ(str); + } +} + +/// Collects `n` buffer lines into array `l` and/or lua_State `lstate`, optionally replacing +/// newlines with NUL. +/// +/// @param buf Buffer to get lines from +/// @param n Number of lines to collect +/// @param replace_nl Replace newlines ("\n") with NUL +/// @param start Line number to start from +/// @param start_idx First index to push to +/// @param[out] l If not NULL, Lines are copied here +/// @param[out] lstate If not NULL, Lines are pushed into a table onto the stack +/// @param err[out] Error, if any +/// @return true unless `err` was set +bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl, + Array *l, lua_State *lstate, Error *err) +{ + for (size_t i = 0; i < n; i++) { + linenr_T lnum = start + (linenr_T)i; + + if (lnum >= MAXLNUM) { + if (err != NULL) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + } + return false; + } + + char *bufstr = ml_get_buf(buf, lnum, false); + push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl); + } + + return true; +} diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h index 1c4a93a587..0814da63cd 100644 --- a/src/nvim/api/buffer.h +++ b/src/nvim/api/buffer.h @@ -1,7 +1,10 @@ #ifndef NVIM_API_BUFFER_H #define NVIM_API_BUFFER_H +#include <lauxlib.h> + #include "nvim/api/private/defs.h" +#include "nvim/buffer_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.h.generated.h" diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 1323fc347b..abd265f2cf 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -1,21 +1,36 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <inttypes.h> #include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/command.h" -#include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" +#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/ops.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/usercmd.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -31,17 +46,18 @@ /// @param[out] err Error details, if any. /// @return Dictionary containing command information, with these keys: /// - cmd: (string) Command name. -/// - range: (array) Command <range>. Can have 0-2 elements depending on how many items the -/// range contains. Has no elements if command doesn't accept a range or if -/// no range was specified, one element if only a single range item was -/// specified and two elements if both range items were specified. -/// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot -/// take a count. -/// - reg: (number) The optional command |<register>|, if specified. Empty string if not -/// specified or if command cannot take a register. +/// - range: (array) (optional) Command range (|<line1>| |<line2>|). +/// Omitted if command doesn't accept a range. +/// Otherwise, has no elements if no range was specified, one element if +/// only a single range item was specified, or two elements if both range +/// items were specified. +/// - count: (number) (optional) Command |<count>|. +/// Omitted if command cannot take a count. +/// - reg: (string) (optional) Command |<register>|. +/// Omitted if command cannot take a register. /// - bang: (boolean) Whether command contains a |<bang>| (!) modifier. /// - args: (array) Command arguments. -/// - addr: (string) Value of |:command-addr|. Uses short name. +/// - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines. /// - nargs: (string) Value of |:command-nargs|. /// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. /// Empty if there isn't a next command. @@ -62,13 +78,14 @@ /// - browse: (boolean) |:browse|. /// - confirm: (boolean) |:confirm|. /// - hide: (boolean) |:hide|. +/// - horizontal: (boolean) |:horizontal|. /// - keepalt: (boolean) |:keepalt|. /// - keepjumps: (boolean) |:keepjumps|. /// - keepmarks: (boolean) |:keepmarks|. /// - keeppatterns: (boolean) |:keeppatterns|. /// - lockmarks: (boolean) |:lockmarks|. /// - noswapfile: (boolean) |:noswapfile|. -/// - tab: (integer) |:tab|. +/// - tab: (integer) |:tab|. -1 when omitted. /// - verbose: (integer) |:verbose|. -1 when omitted. /// - vertical: (boolean) |:vertical|. /// - split: (string) Split modifier string, is an empty string when there's no split @@ -104,7 +121,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) // Parse arguments Array args = ARRAY_DICT_INIT; - size_t length = STRLEN(ea.arg); + size_t length = strlen(ea.arg); // For nargs = 1 or '?', pass the entire argument list as a single argument, // otherwise split arguments by whitespace. @@ -141,15 +158,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); } - if ((ea.argt & EX_RANGE) && ea.addr_count > 0) { + if (ea.argt & EX_RANGE) { Array range = ARRAY_DICT_INIT; - if (ea.addr_count > 1) { - ADD(range, INTEGER_OBJ(ea.line1)); + if (ea.addr_count > 0) { + if (ea.addr_count > 1) { + ADD(range, INTEGER_OBJ(ea.line1)); + } + ADD(range, INTEGER_OBJ(ea.line2)); } - ADD(range, INTEGER_OBJ(ea.line2)); PUT(result, "range", ARRAY_OBJ(range)); - } else { - PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); } if (ea.argt & EX_COUNT) { @@ -160,14 +177,12 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) } else { PUT(result, "count", INTEGER_OBJ(0)); } - } else { - PUT(result, "count", INTEGER_OBJ(-1)); } - char reg[2]; - reg[0] = (char)ea.regname; - reg[1] = '\0'; - PUT(result, "reg", CSTR_TO_OBJ(reg)); + if (ea.argt & EX_REGSTR) { + char reg[2] = { (char)ea.regname, NUL }; + PUT(result, "reg", CSTR_TO_OBJ(reg)); + } PUT(result, "bang", BOOLEAN_OBJ(ea.forceit)); PUT(result, "args", ARRAY_OBJ(args)); @@ -238,7 +253,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); - PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab)); + PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1)); PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1)); PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE)); PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM)); @@ -250,6 +265,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS)); PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE)); PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT)); + PUT(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR)); const char *split; if (cmdinfo.cmdmod.cmod_split & WSP_BOT) { @@ -283,7 +299,11 @@ end: /// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This /// allows for easier construction and manipulation of an Ex command. This also allows for things /// such as having spaces inside a command argument, expanding filenames in a command that otherwise -/// doesn't expand filenames, etc. +/// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String. +/// +/// The first argument may also be used instead of count for commands that support it in order to +/// make their usage simpler with |vim.cmd()|. For example, instead of +/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`. /// /// On execution error: fails with VimL error, updates v:errmsg. /// @@ -308,8 +328,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error char *cmdline = NULL; char *cmdname = NULL; - ArrayOf(String) args; - size_t argc = 0; + ArrayOf(String) args = ARRAY_DICT_INIT; String retv = (String)STRING_INIT; @@ -381,48 +400,70 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (cmd->args.type != kObjectTypeArray) { VALIDATION_ERROR("'args' must be an Array"); } - // Check if every argument is valid + + // Process all arguments. Convert non-String arguments to String and check if String arguments + // have non-whitespace characters. for (size_t i = 0; i < cmd->args.data.array.size; i++) { Object elem = cmd->args.data.array.items[i]; - if (elem.type != kObjectTypeString) { - VALIDATION_ERROR("Command argument must be a String"); - } else if (string_iswhite(elem.data.string)) { - VALIDATION_ERROR("Command argument must have non-whitespace characters"); + char *data_str; + + switch (elem.type) { + case kObjectTypeBoolean: + data_str = xcalloc(2, sizeof(char)); + data_str[0] = elem.data.boolean ? '1' : '0'; + data_str[1] = '\0'; + break; + case kObjectTypeBuffer: + case kObjectTypeWindow: + case kObjectTypeTabpage: + case kObjectTypeInteger: + data_str = xcalloc(NUMBUFLEN, sizeof(char)); + snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer); + break; + case kObjectTypeString: + if (string_iswhite(elem.data.string)) { + VALIDATION_ERROR("String command argument must have at least one non-whitespace " + "character"); + } + data_str = xstrndup(elem.data.string.data, elem.data.string.size); + break; + default: + VALIDATION_ERROR("Invalid type for command argument"); + break; } + + ADD(args, STRING_OBJ(cstr_as_string(data_str))); } - argc = cmd->args.data.array.size; bool argc_valid; // Check if correct number of arguments is used. switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { case EX_EXTRA | EX_NOSPC | EX_NEEDARG: - argc_valid = argc == 1; + argc_valid = args.size == 1; break; case EX_EXTRA | EX_NOSPC: - argc_valid = argc <= 1; + argc_valid = args.size <= 1; break; case EX_EXTRA | EX_NEEDARG: - argc_valid = argc >= 1; + argc_valid = args.size >= 1; break; case EX_EXTRA: argc_valid = true; break; default: - argc_valid = argc == 0; + argc_valid = args.size == 0; break; } if (!argc_valid) { VALIDATION_ERROR("Incorrect number of arguments supplied"); } - - args = cmd->args.data.array; } // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` // since it only ever checks the first argument. - set_cmd_addr_type(&ea, argc > 0 ? args.items[0].data.string.data : NULL); + set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL); if (HAS_KEY(cmd->range)) { if (!(ea.argt & EX_RANGE)) { @@ -557,15 +598,17 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } if (HAS_KEY(mods.tab)) { - if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) { - VALIDATION_ERROR("'mods.tab' must be a non-negative Integer"); + if (mods.tab.type != kObjectTypeInteger) { + VALIDATION_ERROR("'mods.tab' must be an Integer"); + } else if ((int)mods.tab.data.integer >= 0) { + // Silently ignore negative integers to allow mods.tab to be set to -1. + cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1; } - cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1; } if (HAS_KEY(mods.verbose)) { if (mods.verbose.type != kObjectTypeInteger) { - VALIDATION_ERROR("'mods.verbose' must be a Integer"); + VALIDATION_ERROR("'mods.verbose' must be an Integer"); } else if ((int)mods.verbose.data.integer >= 0) { // Silently ignore negative integers to allow mods.verbose to be set to -1. cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1; @@ -576,6 +619,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'"); cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0); + bool horizontal; + OBJ_TO_BOOL(horizontal, mods.horizontal, false, "'mods.horizontal'"); + cmdinfo.cmdmod.cmod_split |= (horizontal ? WSP_HOR : 0); + if (HAS_KEY(mods.split)) { if (mods.split.type != kObjectTypeString) { VALIDATION_ERROR("'mods.split' must be a String"); @@ -583,15 +630,15 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (*mods.split.data.string.data == NUL) { // Empty string, do nothing. - } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 - || STRCMP(mods.split.data.string.data, "leftabove") == 0) { + } else if (strcmp(mods.split.data.string.data, "aboveleft") == 0 + || strcmp(mods.split.data.string.data, "leftabove") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_ABOVE; - } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 - || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { + } else if (strcmp(mods.split.data.string.data, "belowright") == 0 + || strcmp(mods.split.data.string.data, "rightbelow") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_BELOW; - } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) { + } else if (strcmp(mods.split.data.string.data, "topleft") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_TOP; - } else if (STRCMP(mods.split.data.string.data, "botright") == 0) { + } else if (strcmp(mods.split.data.string.data, "botright") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_BOT; } else { VALIDATION_ERROR("Invalid value for 'mods.split'"); @@ -613,6 +660,12 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'"); OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'"); + if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) { + // CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't + // work properly. + cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT; + } + if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) { VALIDATION_ERROR("Command cannot be run in sandbox"); } @@ -620,7 +673,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error // Finally, build the command line string that will be stored inside ea.cmdlinep. // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. - build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc); + build_cmdline_str(&cmdline, &ea, &cmdinfo, args); ea.cmdlinep = &cmdline; garray_T capture_local; @@ -676,6 +729,7 @@ clear_ga: ga_clear(&capture_local); } end: + api_free_array(args); xfree(cmdline); xfree(cmdname); xfree(ea.args); @@ -705,8 +759,9 @@ static bool string_iswhite(String str) /// Build cmdline string for command, used by `nvim_cmd()`. static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, - ArrayOf(String) args, size_t argc) + ArrayOf(String) args) { + size_t argc = args.size; StringBuilder cmdline = KV_INITIAL_VALUE; kv_resize(cmdline, 32); // Make it big enough to handle most typical commands @@ -753,6 +808,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin } while (0) CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_VERT, "vertical "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_HOR, "horizontal "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_BROWSE, "browse "); @@ -791,7 +847,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin } eap->argc = argc; - eap->arglens = xcalloc(argc, sizeof(size_t)); + eap->arglens = eap->argc > 0 ? xcalloc(argc, sizeof(size_t)) : NULL; size_t argstart_idx = cmdline.size; for (size_t i = 0; i < argc; i++) { String s = args.items[i].data.string; @@ -806,7 +862,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin // Now that all the arguments are appended, use the command index and argument indices to set the // values of eap->cmd, eap->arg and eap->args. eap->cmd = cmdline.items + cmdname_idx; - eap->args = xcalloc(argc, sizeof(char *)); + eap->args = eap->argc > 0 ? xcalloc(argc, sizeof(char *)) : NULL; size_t offset = argstart_idx; for (size_t i = 0; i < argc; i++) { offset++; // Account for space @@ -823,13 +879,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin // Replace, :make and :grep with 'makeprg' and 'grepprg'. char *p = replace_makeprg(eap, eap->arg, cmdlinep); if (p != eap->arg) { - // If replace_makeprg modified the cmdline string, correct the argument pointers. + // If replace_makeprg() modified the cmdline string, correct the eap->arg pointer. eap->arg = p; - // We can only know the position of the first argument because the argument list can be used - // multiple times in makeprg / grepprg. - if (argc >= 1) { - eap->args[0] = p; - } + // This cannot be a user command, so eap->args will not be used. + XFREE_CLEAR(eap->args); + XFREE_CLEAR(eap->arglens); + eap->argc = 0; } } @@ -840,7 +895,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// {command} is the replacement text or Lua function to execute. /// /// Example: -/// <pre> +/// <pre>vim /// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) /// :SayHello /// Hello world! @@ -850,6 +905,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// @param command Replacement command to execute when this user command is executed. When called /// from Lua, the command can also be a Lua function. The function is called with a /// single table argument that contains the following keys: +/// - name: (string) Command name /// - args: (string) The args passed to the command, if any |<args>| /// - fargs: (table) The args split by unescaped whitespace (when more than one /// argument is allowed), if any |<f-args>| @@ -929,7 +985,7 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) for (int i = 0; i < gap->ga_len; i++) { ucmd_T *cmd = USER_CMD_GA(gap, i); - if (!STRCMP(name.data, cmd->uc_name)) { + if (!strcmp(name.data, cmd->uc_name)) { free_ucmd(cmd); gap->ga_len -= 1; diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index abaac07755..332e2b5fc3 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,7 +1,6 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -11,10 +10,15 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/api/vimscript.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/extmark.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/deprecated.c.generated.h" @@ -190,7 +194,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) String rv = { .size = 0 }; index = convert_index(index); - Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, err); + Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, NULL, err); if (!ERROR_SET(err) && slice.size) { rv = slice.items[0].data.string; @@ -263,7 +267,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - return nvim_buf_get_lines(0, buffer, start, end, false, err); + return nvim_buf_get_lines(0, buffer, start, end, false, NULL, err); } /// Replaces a line range on the buffer diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 933aa85530..ab3b3485e4 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1,20 +1,29 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <stdbool.h> #include <stdint.h> -#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/highlight_group.h" -#include "nvim/lua/executor.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/strings.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -31,7 +40,7 @@ void api_extmark_free_all_mem(void) map_destroy(String, handle_T)(&namespace_ids); } -/// Creates a new \*namespace\* or gets an existing one. +/// 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()|. @@ -51,13 +60,13 @@ Integer nvim_create_namespace(String name) } id = next_namespace_id++; if (name.size > 0) { - String name_alloc = copy_string(name); + String name_alloc = copy_string(name, NULL); map_put(String, handle_T)(&namespace_ids, name_alloc, id); } return (Integer)id; } -/// Gets existing, non-anonymous namespaces. +/// Gets existing, non-anonymous |namespace|s. /// /// @return dict that maps from names to namespace ids. Dictionary nvim_get_namespaces(void) @@ -87,7 +96,7 @@ const char *describe_ns(NS ns_id) } // Is the Namespace in use? -static bool ns_initialized(uint32_t ns) +bool ns_initialized(uint32_t ns) { if (ns < 1) { return false; @@ -186,7 +195,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict return rv; } -/// Gets the position (0-indexed) of an extmark. +/// Gets the position (0-indexed) of an |extmark|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| @@ -240,31 +249,29 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return extmark_to_array(&extmark, false, details); } -/// Gets extmarks in "traversal order" from a |charwise| region defined by +/// Gets |extmarks| in "traversal order" from a |charwise| region defined by /// buffer positions (inclusive, 0-indexed |api-indexing|). /// /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) /// respectively, thus the following are equivalent: -/// -/// <pre> -/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) +/// <pre>lua +/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) /// </pre> /// /// If `end` is less than `start`, traversal works backwards. (Useful /// with `limit`, to get the first marks prior to a given position.) /// /// Example: -/// -/// <pre> +/// <pre>lua /// local a = vim.api /// local pos = a.nvim_win_get_cursor(0) /// local ns = a.nvim_create_namespace('my-plugin') /// -- Create new extmark at line 1, column 1. /// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {}) /// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {}) /// -- Get extmarks only from line 3. /// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) /// -- Get all marks in this buffer + namespace. @@ -361,7 +368,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } -/// Creates or updates an extmark. +/// Creates or updates an |extmark|. /// /// By default a new extmark is created when no id is passed in, but it is also /// possible to create a new mark by passing in a previously unused id or move @@ -389,11 +396,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - virt_text : virtual text to link to this mark. /// A list of [text, highlight] tuples, each representing a /// text chunk with specified highlight. `highlight` element -/// can either be a a single highlight group, or an array of +/// can either be a single highlight group, or an array of /// multiple highlight groups that will be stacked /// (highest priority last). A highlight group can be supplied /// either as a string or as an integer, the latter which -/// can be obtained using |nvim_get_hl_id_by_name|. +/// can be obtained using |nvim_get_hl_id_by_name()|. /// - virt_text_pos : position of virtual text. Possible values: /// - "eol": right after eol character (default) /// - "overlay": display over the specified column, without @@ -430,7 +437,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// column of the window, bypassing /// sign and number columns. /// -/// - ephemeral : for use with |nvim_set_decoration_provider| +/// - ephemeral : for use with |nvim_set_decoration_provider()| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the /// buffer. @@ -473,6 +480,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// When a character is supplied it is used as |:syn-cchar|. /// "hl_group" is used as highlight for the cchar if provided, /// otherwise it defaults to |hl-Conceal|. +/// - spell: boolean indicating that spell checking should be +/// performed within this extmark /// - ui_watched: boolean that indicates the mark should be drawn /// by a UI. When set, the UI will receive win_extmark events. /// Note: the mark is positioned by virt_text attributes. Can be @@ -689,7 +698,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } if (opts->sign_text.type == kObjectTypeString) { - if (!init_sign_text((char **)&decor.sign_text, + if (!init_sign_text(&decor.sign_text, opts->sign_text.data.string.data)) { api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); goto error; @@ -719,6 +728,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); + if (opts->spell.type == kObjectTypeNil) { + decor.spell = kNone; + } else { + bool spell = false; + OPTION_TO_BOOL(spell, spell, false); + decor.spell = spell ? kTrue : kFalse; + has_decor = true; + } + OPTION_TO_BOOL(decor.ui_watched, ui_watched, false); if (decor.ui_watched) { has_decor = true; @@ -735,7 +753,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer line = buf->b_ml.ml_line_count; } } else if (line < buf->b_ml.ml_line_count) { - len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line + 1, false)); + len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false)); } if (col == -1) { @@ -754,7 +772,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (col2 >= 0) { if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { - len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false)); + len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1, false)); } else if (line2 == buf->b_ml.ml_line_count) { // We are trying to add an extmark past final newline len = 0; @@ -796,7 +814,7 @@ error: return 0; } -/// Removes an extmark. +/// Removes an |extmark|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| @@ -826,9 +844,8 @@ uint32_t src2ns(Integer *src_id) } if (*src_id < 0) { return (((uint32_t)1) << 31) - 1; - } else { - return (uint32_t)(*src_id); } + return (uint32_t)(*src_id); } /// Adds a highlight to buffer. @@ -912,7 +929,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In return ns_id; } -/// Clears namespaced objects (highlights, extmarks, virtual text) from +/// Clears |namespace|d objects (highlights, |extmarks|, virtual text) from /// a region. /// /// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire @@ -945,13 +962,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, (int)line_end - 1, MAXCOL); } -/// Set or change decoration provider for a namespace +/// Set or change decoration provider for a |namespace| /// /// This is a very general purpose interface for having lua callbacks /// being triggered during the redraw code. /// -/// The expected usage is to set extmarks for the currently -/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks +/// The expected usage is to set |extmarks| for the currently +/// redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks /// on a per-window or per-lines basis. Use the `ephemeral` key to only /// use the mark for the current screen redraw (the callback will be called /// again for the next redraw ). @@ -972,20 +989,21 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// for the moment. /// /// @param ns_id Namespace id from |nvim_create_namespace()| -/// @param opts Callbacks invoked during redraw: +/// @param opts Table of callbacks: /// - on_start: called first on each screen redraw /// ["start", tick] -/// - on_buf: called for each buffer being redrawn (before window -/// callbacks) +/// - on_buf: called for each buffer being redrawn (before +/// window callbacks) /// ["buf", bufnr, tick] -/// - on_win: called when starting to redraw a specific window. +/// - on_win: called when starting to redraw a +/// specific window. /// ["win", winid, bufnr, topline, botline_guess] -/// - on_line: called for each buffer line being redrawn. (The -/// interaction with fold lines is subject to change) +/// - on_line: called for each buffer line being redrawn. +/// (The interaction with fold lines is subject to change) /// ["win", winid, bufnr, row] /// - on_end: called at the end of a redraw cycle /// ["end", tick] -void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err) +void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *opts, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY { DecorProvider *p = get_decor_provider((NS)ns_id, true); @@ -993,41 +1011,36 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw - redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? + redraw_all_later(UPD_NOT_VALID); // TODO(bfredl): too soon? struct { const char *name; + Object *source; LuaRef *dest; } cbs[] = { - { "on_start", &p->redraw_start }, - { "on_buf", &p->redraw_buf }, - { "on_win", &p->redraw_win }, - { "on_line", &p->redraw_line }, - { "on_end", &p->redraw_end }, - { "_on_hl_def", &p->hl_def }, - { NULL, NULL }, + { "on_start", &opts->on_start, &p->redraw_start }, + { "on_buf", &opts->on_buf, &p->redraw_buf }, + { "on_win", &opts->on_win, &p->redraw_win }, + { "on_line", &opts->on_line, &p->redraw_line }, + { "on_end", &opts->on_end, &p->redraw_end }, + { "_on_hl_def", &opts->_on_hl_def, &p->hl_def }, + { "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav }, + { NULL, NULL, NULL }, }; - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - size_t j; - for (j = 0; cbs[j].name && cbs[j].dest; j++) { - if (strequal(cbs[j].name, k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, - "%s is not a function", cbs[j].name); - goto error; - } - *(cbs[j].dest) = v->data.luaref; - v->data.luaref = LUA_NOREF; - break; - } + for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) { + Object *v = cbs[i].source; + if (v->type == kObjectTypeNil) { + continue; } - if (!cbs[j].name) { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", cbs[i].name); goto error; } + *(cbs[i].dest) = v->data.luaref; + v->data.luaref = LUA_NOREF; } p->active = true; @@ -1038,7 +1051,7 @@ error: decor_provider_clear(p); } -/// Gets the line and column of an extmark. +/// Gets the line and column of an |extmark|. /// /// Extmarks may be queried by position, name or even special names /// in the future such as "cursor". @@ -1103,7 +1116,7 @@ static int init_sign_text(char **sign_text, char *text) { char *s; - char *endp = text + (int)STRLEN(text); + char *endp = text + (int)strlen(text); // Count cells and check for non-printable chars int cells = 0; diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h index 74802c6efb..0a627a889c 100644 --- a/src/nvim/api/extmark.h +++ b/src/nvim/api/extmark.h @@ -3,7 +3,10 @@ #include "nvim/api/private/defs.h" #include "nvim/decoration.h" +#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/map_defs.h" +#include "nvim/types.h" EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT); EXTERN handle_T next_namespace_id INIT(= 1); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 6fad52ba75..30dcef6127 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -1,8 +1,17 @@ return { - context = { + { 'context', { "types"; - }; - set_extmark = { + }}; + { 'set_decoration_provider', { + "on_start"; + "on_buf"; + "on_win"; + "on_line"; + "on_end"; + "_on_hl_def"; + "_on_spell_nav"; + }}; + { 'set_extmark', { "id"; "end_line"; "end_row"; @@ -28,9 +37,10 @@ return { "line_hl_group"; "cursorline_hl_group"; "conceal"; + "spell"; "ui_watched"; - }; - keymap = { + }}; + { 'keymap', { "noremap"; "nowait"; "silent"; @@ -40,11 +50,11 @@ return { "callback"; "desc"; "replace_keycodes"; - }; - get_commands = { + }}; + { 'get_commands', { "builtin"; - }; - user_command = { + }}; + { 'user_command', { "addr"; "bang"; "bar"; @@ -57,8 +67,8 @@ return { "preview"; "range"; "register"; - }; - float_config = { + }}; + { 'float_config', { "row"; "col"; "width"; @@ -71,27 +81,29 @@ return { "focusable"; "zindex"; "border"; + "title"; + "title_pos"; "style"; "noautocmd"; - }; - runtime = { + }}; + { 'runtime', { "is_lua"; "do_source"; - }; - eval_statusline = { + }}; + { 'eval_statusline', { "winid"; "maxwidth"; "fillchar"; "highlights"; "use_winbar"; "use_tabline"; - }; - option = { + }}; + { 'option', { "scope"; "win"; "buf"; - }; - highlight = { + }}; + { 'highlight', { "bold"; "standout"; "strikethrough"; @@ -102,6 +114,7 @@ return { "underdashed"; "italic"; "reverse"; + "altfont"; "nocombine"; "default"; "cterm"; @@ -114,8 +127,10 @@ return { "global_link"; "fallback"; "blend"; - }; - highlight_cterm = { + "fg_indexed"; + "bg_indexed"; + }}; + { 'highlight_cterm', { "bold"; "standout"; "strikethrough"; @@ -126,16 +141,17 @@ return { "underdashed"; "italic"; "reverse"; + "altfont"; "nocombine"; - }; + }}; -- Autocmds - clear_autocmds = { + { 'clear_autocmds', { "buffer"; "event"; "group"; "pattern"; - }; - create_autocmd = { + }}; + { 'create_autocmd', { "buffer"; "callback"; "command"; @@ -144,24 +160,24 @@ return { "nested"; "once"; "pattern"; - }; - exec_autocmds = { + }}; + { 'exec_autocmds', { "buffer"; "group"; "modeline"; "pattern"; "data"; - }; - get_autocmds = { + }}; + { 'get_autocmds', { "event"; "group"; "pattern"; "buffer"; - }; - create_augroup = { + }}; + { 'create_augroup', { "clear"; - }; - cmd = { + }}; + { 'cmd', { "cmd"; "range"; "count"; @@ -173,12 +189,12 @@ return { "nargs"; "addr"; "nextcmd"; - }; - cmd_magic = { + }}; + { 'cmd_magic', { "file"; "bar"; - }; - cmd_mods = { + }}; + { 'cmd_mods', { "silent"; "emsg_silent"; "unsilent"; @@ -188,6 +204,7 @@ return { "browse"; "confirm"; "hide"; + "horizontal"; "keepalt"; "keepjumps"; "keepmarks"; @@ -198,13 +215,15 @@ return { "verbose"; "vertical"; "split"; - }; - cmd_mods_filter = { + }}; + { 'cmd_mods_filter', { "pattern"; "force"; - }; - cmd_opts = { + }}; + { 'cmd_opts', { "output"; - }; + }}; + { 'echo_opts', { + "verbose"; + }}; } - diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 867584dd71..bfcb99754f 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -1,20 +1,21 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> -#include <stddef.h> -#include <stdlib.h> #include <string.h> #include "nvim/api/options.h" -#include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/autocmd.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/eval/window.h" +#include "nvim/globals.h" +#include "nvim/memory.h" #include "nvim/option.h" -#include "nvim/option_defs.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -147,7 +148,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) /// @param name Option name /// @param value New option value /// @param opts Optional parameters -/// - scope: One of 'global' or 'local'. Analogous to +/// - scope: One of "global" or "local". Analogous to /// |:setglobal| and |:setlocal|, respectively. /// - win: |window-ID|. Used for setting window local option. /// - buf: Buffer number. Used for setting buffer local option. @@ -202,7 +203,7 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error /// Gets the option information for all options. /// /// The dictionary has the full option names as keys and option metadata -/// dictionaries as detailed at |nvim_get_option_info|. +/// dictionaries as detailed at |nvim_get_option_info()|. /// /// @return dictionary of all options Dictionary nvim_get_all_options_info(Error *err) @@ -256,7 +257,7 @@ void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) /// @param name Option name /// @param[out] err Error details, if any /// @return Option value (global) -Object nvim_get_option(String name, Error *err) +Object nvim_get_option(String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { return get_option_from(NULL, SREQ_GLOBAL, name, err); @@ -268,7 +269,7 @@ Object nvim_get_option(String name, Error *err) /// @param name Option name /// @param[out] err Error details, if any /// @return Option value -Object nvim_buf_get_option(Buffer buffer, String name, Error *err) +Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -306,7 +307,7 @@ void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object /// @param name Option name /// @param[out] err Error details, if any /// @return Option value -Object nvim_win_get_option(Window window, String name, Error *err) +Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -346,7 +347,7 @@ void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object /// @param name The option name /// @param[out] err Details of an error that may have occurred /// @return the option value -Object get_option_from(void *from, int type, String name, Error *err) +static Object get_option_from(void *from, int type, String name, Error *err) { Object rv = OBJECT_INIT; @@ -358,8 +359,7 @@ Object get_option_from(void *from, int type, String name, Error *err) // Return values int64_t numval; char *stringval = NULL; - int flags = get_option_value_strict(name.data, &numval, &stringval, - type, from); + int flags = get_option_value_strict(name.data, &numval, &stringval, type, from); if (!flags) { api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'", @@ -487,7 +487,7 @@ static getoption_T access_option_value(char *key, long *numval, char **stringval bool get, Error *err) { if (get) { - return get_option_value(key, numval, stringval, opt_flags); + return get_option_value(key, numval, stringval, NULL, opt_flags); } else { char *errmsg; if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) { diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 8724ef4432..58ff552ab7 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -2,17 +2,24 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <stdlib.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/lua/converter.h" +#include "nvim/garray.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/vim.h" /// Helper structure for vim_to_object typedef struct { @@ -65,8 +72,8 @@ typedef struct { #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ ufunc_T *fp = find_func(fun); \ - if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ - LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + if (fp != NULL && (fp->uf_flags & FC_LUAREF)) { \ + LuaRef ref = api_new_luaref(fp->uf_luaref); \ kvi_push(edata->stack, LUAREF_OBJ(ref)); \ } else { \ TYPVAL_ENCODE_CONV_NIL(tv); \ @@ -351,10 +358,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) } case kObjectTypeLuaRef: { - LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = api_new_luaref(obj.data.luaref); - char *name = - (char *)register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); + char *name = register_luafunc(api_new_luaref(obj.data.luaref)); tv->v_type = VAR_FUNC; tv->vval.v_string = xstrdup(name); break; diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 9c7e59e4b3..8acbf0d9de 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -5,14 +5,14 @@ #include <stdint.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/func_attr.h" -#include "nvim/lib/kvec.h" #include "nvim/types.h" #define ARRAY_DICT_INIT KV_INITIAL_VALUE #define STRING_INIT { .data = NULL, .size = 0 } #define OBJECT_INIT { .type = kObjectTypeNil } -#define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } +#define ERROR_INIT ((Error) { .type = kErrorTypeNone, .msg = NULL }) #define REMOTE_TYPE(type) typedef handle_T type #define ERROR_SET(e) ((e)->type != kErrorTypeNone) @@ -48,7 +48,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(const uint64_t channel_id) +static inline bool is_internal_call(uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index d6a6fc1219..f427bba00e 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -1,38 +1,11 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> -#include <inttypes.h> -#include <msgpack.h> -#include <stdbool.h> +#include <stddef.h> -#include "nvim/api/deprecated.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" -#include "nvim/log.h" -#include "nvim/map.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" - -// =========================================================================== -// NEW API FILES MUST GO HERE. -// -// When creating a new API file, you must include it here, -// so that the dispatcher can find the C functions that you are creating! -// =========================================================================== -#include "nvim/api/autocmd.h" -#include "nvim/api/buffer.h" -#include "nvim/api/command.h" -#include "nvim/api/extmark.h" -#include "nvim/api/options.h" -#include "nvim/api/tabpage.h" -#include "nvim/api/ui.h" -#include "nvim/api/vim.h" -#include "nvim/api/vimscript.h" -#include "nvim/api/win_config.h" -#include "nvim/api/window.h" -#include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch_wrappers.generated.h" diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index 4b7c394944..4ae61b2bfb 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -1,22 +1,29 @@ #ifndef NVIM_API_PRIVATE_DISPATCH_H #define NVIM_API_PRIVATE_DISPATCH_H +#include <stdbool.h> +#include <stdint.h> + #include "nvim/api/private/defs.h" +#include "nvim/memory.h" +#include "nvim/types.h" -typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, - Array args, - Error *error); +typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error); /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// functions of this type. -typedef struct { +struct MsgpackRpcRequestHandler { const char *name; ApiDispatchWrapper fn; bool fast; // Function is safe to be executed immediately while running the // uv loop (the loop is run very frequently due to breakcheck). // If "fast" is false, the function is deferred, i e the call will // be put in the event queue, for safe handling later. -} MsgpackRpcRequestHandler; + bool arena_return; // return value is allocated in the arena (or statically) + // and should not be freed as such. +}; + +extern const MsgpackRpcRequestHandler method_handlers[]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch.h.generated.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index c466fc53e1..519f2cc5bf 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -3,37 +3,36 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> +#include <msgpack/unpack.h> +#include <stdarg.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" +#include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_eval.h" -#include "nvim/extmark.h" +#include "nvim/garray.h" #include "nvim/highlight_group.h" -#include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" #include "nvim/map.h" -#include "nvim/map_defs.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/msgpack_rpc/helpers.h" +#include "nvim/pos.h" #include "nvim/ui.h" #include "nvim/version.h" -#include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/funcs_metadata.generated.h" @@ -58,6 +57,7 @@ void try_enter(TryState *const tstate) .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, + .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -65,6 +65,7 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; + did_throw = false; need_rethrow = false; did_emsg = false; } @@ -85,6 +86,7 @@ bool try_leave(const TryState *const tstate, Error *const 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); @@ -93,6 +95,7 @@ bool try_leave(const TryState *const tstate, Error *const err) 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; @@ -127,7 +130,7 @@ bool try_end(Error *err) force_abort = false; if (got_int) { - if (current_exception) { + if (did_throw) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -146,8 +149,19 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (current_exception) { - api_set_error(err, kErrorTypeException, "%s", current_exception->value); + } else if (did_throw) { + if (*current_exception->throw_name != NUL) { + if (current_exception->throw_lnum != 0) { + api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s", + current_exception->throw_name, current_exception->throw_lnum, + current_exception->value); + } else { + api_set_error(err, kErrorTypeException, "%s: %s", + current_exception->throw_name, current_exception->value); + } + } else { + api_set_error(err, kErrorTypeException, "%s", current_exception->value); + } discard_current_exception(); } @@ -214,6 +228,8 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva return rv; } + bool watched = tv_dict_is_watched(dict); + if (del) { // Delete the key if (di == NULL) { @@ -221,6 +237,10 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); } else { + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv); + } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); @@ -237,11 +257,16 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva return rv; } + typval_T oldtv = TV_INITIAL_VALUE; + if (di == NULL) { // Need to create an entry di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); @@ -251,6 +276,13 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva // Update the value tv_copy(&tv, &di->di_tv); + + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, &tv, &oldtv); + tv_clear(&oldtv); + } + // Clear the temporary variable tv_clear(&tv); } @@ -369,7 +401,13 @@ String cbuf_to_string(const char *buf, size_t size) String cstrn_to_string(const char *str, size_t maxsize) FUNC_ATTR_NONNULL_ALL { - return cbuf_to_string(str, STRNLEN(str, maxsize)); + return cbuf_to_string(str, strnlen(str, maxsize)); +} + +String cstrn_as_string(char *str, size_t maxsize) + FUNC_ATTR_NONNULL_ALL +{ + return cbuf_as_string(str, strnlen(str, maxsize)); } /// Creates a String using the given C string. Unlike @@ -440,53 +478,15 @@ Array string_to_array(const String input, bool crlf) return ret; } -/// Collects `n` buffer lines into array `l`, optionally replacing newlines -/// with NUL. -/// -/// @param buf Buffer to get lines from -/// @param n Number of lines to collect -/// @param replace_nl Replace newlines ("\n") with NUL -/// @param start Line number to start from -/// @param[out] l Lines are copied here -/// @param err[out] Error, if any -/// @return true unless `err` was set -bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Array *l, Error *err) -{ - for (size_t i = 0; i < n; i++) { - int64_t lnum = start + (int64_t)i; - - if (lnum >= MAXLNUM) { - if (err != NULL) { - api_set_error(err, kErrorTypeValidation, "Line index is too high"); - } - return false; - } - - const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); - Object str = STRING_OBJ(cstr_to_string(bufstr)); - - if (replace_nl) { - // Vim represents NULs as NLs, but this may confuse clients. - strchrsub(str.data.string.data, '\n', '\0'); - } - - l->items[i] = str; - } - - return true; -} - /// Returns a substring of a buffer line /// /// @param buf Buffer handle /// @param lnum Line number (1-based) /// @param start_col Starting byte offset into line (0-based) /// @param end_col Ending byte offset into line (0-based, exclusive) -/// @param replace_nl Replace newlines ('\n') with null ('\0') /// @param err Error object /// @return The text between start_col and end_col on line lnum of buffer buf -String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl, - Error *err) +String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, Error *err) { String rv = STRING_INIT; @@ -495,7 +495,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false); size_t line_length = strlen(bufstr); start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; @@ -515,12 +515,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col)); - if (replace_nl) { - strchrsub(rv.data, '\n', '\0'); - } - - return rv; + return cstrn_as_string(&bufstr[start_col], (size_t)(end_col - start_col)); } void api_free_string(String value) @@ -618,6 +613,7 @@ void api_clear_error(Error *value) value->type = kErrorTypeNone; } +/// @returns a shared value. caller must not modify it! Dictionary api_metadata(void) { static Dictionary metadata = ARRAY_DICT_INIT; @@ -630,7 +626,7 @@ Dictionary api_metadata(void) init_type_metadata(&metadata); } - return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; + return metadata; } static void init_function_metadata(Dictionary *metadata) @@ -715,36 +711,40 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } -String copy_string(String str) +// all the copy_[object] functions allow arena=NULL, +// then global allocations are used, and the resulting object +// should be freed with an api_free_[object] function + +String copy_string(String str, Arena *arena) { if (str.data != NULL) { - return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + return (String){ .data = arena_memdupz(arena, str.data, str.size), .size = str.size }; } else { return (String)STRING_INIT; } } -Array copy_array(Array array) +Array copy_array(Array array, Arena *arena) { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, array.size); for (size_t i = 0; i < array.size; i++) { - ADD(rv, copy_object(array.items[i])); + ADD(rv, copy_object(array.items[i], arena)); } return rv; } -Dictionary copy_dictionary(Dictionary dict) +Dictionary copy_dictionary(Dictionary dict, Arena *arena) { - Dictionary rv = ARRAY_DICT_INIT; + Dictionary rv = arena_dict(arena, dict.size); for (size_t i = 0; i < dict.size; i++) { KeyValuePair item = dict.items[i]; - PUT(rv, item.key.data, copy_object(item.value)); + PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena)); } return rv; } /// Creates a deep clone of an object -Object copy_object(Object obj) +Object copy_object(Object obj, Arena *arena) { switch (obj.type) { case kObjectTypeBuffer: @@ -757,13 +757,13 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(copy_string(obj.data.string)); + return STRING_OBJ(copy_string(obj.data.string, arena)); case kObjectTypeArray: - return ARRAY_OBJ(copy_array(obj.data.array)); + return ARRAY_OBJ(copy_array(obj.data.array, arena)); case kObjectTypeDictionary: - return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); + return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary, arena)); case kObjectTypeLuaRef: return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); @@ -844,7 +844,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) goto free_exit; } - String str = copy_string(chunk.items[0].data.string); + String str = copy_string(chunk.items[0].data.string, NULL); int attr = 0; if (chunk.size == 2) { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 4608554448..ec97ba9ec6 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -1,11 +1,17 @@ #ifndef NVIM_API_PRIVATE_HELPERS_H #define NVIM_API_PRIVATE_HELPERS_H +#include <stdbool.h> +#include <stddef.h> + +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/decoration.h" #include "nvim/ex_eval_defs.h" #include "nvim/getchar.h" -#include "nvim/lib/kvec.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/map.h" #include "nvim/memory.h" #include "nvim/vim.h" @@ -134,6 +140,7 @@ typedef struct { const msglist_T *const *msg_list; int trylevel; int got_int; + bool did_throw; int need_rethrow; int did_emsg; } TryState; diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index b81fc3b7d7..21eb326c3b 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -2,13 +2,14 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <stdbool.h> -#include <stdint.h> #include <stdlib.h> #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/tabpage.h" #include "nvim/api/vim.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/window.h" @@ -110,15 +111,14 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) if (tab == curtab) { return nvim_get_current_win(); - } else { - FOR_ALL_WINDOWS_IN_TAB(wp, tab) { - if (wp == tab->tp_curwin) { - return wp->handle; - } + } + FOR_ALL_WINDOWS_IN_TAB(wp, tab) { + if (wp == tab->tp_curwin) { + return wp->handle; } - // There should always be a current window for a tabpage - abort(); } + // There should always be a current window for a tabpage + abort(); } /// Gets the tabpage number diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 6f7bfa244a..e67607a7e4 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -2,67 +2,41 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <inttypes.h> +#include <msgpack/pack.h> #include <stdbool.h> -#include <stddef.h> #include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/autocmd.h" #include "nvim/channel.h" -#include "nvim/cursor_shape.h" +#include "nvim/event/loop.h" +#include "nvim/event/wstream.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/main.h" #include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" -#include "nvim/popupmenu.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/window.h" - -typedef struct { - uint64_t channel_id; - -#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data. - /// guaranteed size available for each new event (so packing of simple events - /// and the header of grid_line will never fail) -#define EVENT_BUF_SIZE 256 - char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data - char *buf_wptr; ///< write head of buffer - const char *cur_event; ///< name of current event (might get multiple arglists) - Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!) - - // state for write_cb, while packing a single arglist to msgpack. This - // might fail due to buffer overflow. - size_t pack_totlen; - bool buf_overflow; - char *temp_buf; - - // We start packing the two outermost msgpack arrays before knowing the total - // number of elements. Thus track the location where array size will need - // to be written in the msgpack buffer, once the specific array is finished. - char *nevents_pos; - char *ncalls_pos; - uint32_t nevents; ///< number of distinct events (top-level args to "redraw" - uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!) - bool flushed_events; ///< events where sent to client without "flush" event - - int hl_id; // Current highlight for legacy put event. - Integer cursor_row, cursor_col; // Intended visible cursor position. - - // Position of legacy cursor, used both for drawing and visible user cursor. - Integer client_row, client_col; - bool wildmenu_active; -} UIData; #define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf)) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" -# include "ui_events_remote.generated.h" +# include "ui_events_remote.generated.h" // IWYU pragma: export #endif static PMap(uint64_t) connected_uis = MAP_INIT; @@ -122,7 +96,7 @@ static char *mpack_array_dyn16(char **buf) static void mpack_str(char **buf, const char *str) { assert(sizeof(schar_T) - 1 < 0x20); - size_t len = STRLEN(str); + size_t len = strlen(str); mpack_w(buf, 0xa0 | len); memcpy(*buf, str, len); *buf += len; @@ -137,8 +111,6 @@ void remote_ui_disconnect(uint64_t channel_id) UIData *data = ui->data; kv_destroy(data->call_buf); pmap_del(uint64_t)(&connected_uis, channel_id); - xfree(data); - ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui, channel_id); xfree(ui); } @@ -198,31 +170,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->pum_col = -1.0; ui->rgb = true; ui->override = false; - ui->grid_resize = remote_ui_grid_resize; - ui->grid_clear = remote_ui_grid_clear; - ui->grid_cursor_goto = remote_ui_grid_cursor_goto; - ui->mode_info_set = remote_ui_mode_info_set; - ui->update_menu = remote_ui_update_menu; - ui->busy_start = remote_ui_busy_start; - ui->busy_stop = remote_ui_busy_stop; - ui->mouse_on = remote_ui_mouse_on; - ui->mouse_off = remote_ui_mouse_off; - ui->mode_change = remote_ui_mode_change; - ui->grid_scroll = remote_ui_grid_scroll; - ui->hl_attr_define = remote_ui_hl_attr_define; - ui->hl_group_set = remote_ui_hl_group_set; - ui->raw_line = remote_ui_raw_line; - ui->bell = remote_ui_bell; - ui->visual_bell = remote_ui_visual_bell; - ui->default_colors_set = remote_ui_default_colors_set; - ui->flush = remote_ui_flush; - ui->suspend = remote_ui_suspend; - ui->set_title = remote_ui_set_title; - ui->set_icon = remote_ui_set_icon; - ui->option_set = remote_ui_option_set; - ui->msg_set_pos = remote_ui_msg_set_pos; - ui->event = remote_ui_event; - ui->inspect = remote_ui_inspect; CLEAR_FIELD(ui->ui_ext); @@ -245,7 +192,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->ui_ext[kUICmdline] = true; } - UIData *data = xmalloc(sizeof(UIData)); + UIData *data = ui->data; data->channel_id = channel_id; data->cur_event = NULL; data->hl_id = 0; @@ -260,7 +207,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona data->wildmenu_active = false; data->call_buf = (Array)ARRAY_DICT_INIT; kv_ensure_space(data->call_buf, 16); - ui->data = data; pmap_put(uint64_t)(&connected_uis, channel_id, ui); ui_attach_impl(ui, channel_id); @@ -276,6 +222,19 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl api_free_dictionary(opts); } +/// Tells the nvim server if focus was gained or lost by the GUI +void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error) + FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(&connected_uis, channel_id)) { + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + + do_autocmd_focusgained((bool)gained); +} + /// Deactivates UI events on the channel. /// /// Removes the client from the list of UIs. |nvim_list_uis()| @@ -293,6 +252,10 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) remote_ui_disconnect(channel_id); } +// TODO(bfredl): use me to detach a specifc ui from the server +void remote_ui_stop(UI *ui) +{} + void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { @@ -395,6 +358,24 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e return; } + if (strequal(name.data, "stdin_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean"); + return; + } + stdin_isatty = value.data.boolean; + return; + } + + if (strequal(name.data, "stdout_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean"); + return; + } + stdout_isatty = value.data.boolean; + return; + } + // LEGACY: Deprecated option, use `ext_cmdline` instead. bool is_popupmenu = strequal(name.data, "popupmenu_external"); @@ -452,7 +433,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I } } -/// Tells Nvim the number of elements displaying in the popumenu, to decide +/// Tells Nvim the number of elements displaying in the popupmenu, to decide /// <PageUp> and <PageDown> movement. /// /// @param channel_id @@ -482,7 +463,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err) ui->pum_nlines = (int)height; } -/// Tells Nvim the geometry of the popumenu, to align floating windows with an +/// Tells Nvim the geometry of the popupmenu, to align floating windows with an /// external popup menu. /// /// Note that this method is not to be confused with |nvim_ui_pum_set_height()|, @@ -646,7 +627,7 @@ static void push_call(UI *ui, const char *name, Array args) data->ncalls++; } -static void remote_ui_grid_clear(UI *ui, Integer grid) +void remote_ui_grid_clear(UI *ui, Integer grid) { UIData *data = ui->data; Array args = data->call_buf; @@ -657,7 +638,7 @@ static void remote_ui_grid_clear(UI *ui, Integer grid) push_call(ui, name, args); } -static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) +void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) { UIData *data = ui->data; Array args = data->call_buf; @@ -670,8 +651,8 @@ static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer h push_call(ui, name, args); } -static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, - Integer right, Integer rows, Integer cols) +void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, + Integer right, Integer rows, Integer cols) { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { @@ -707,8 +688,8 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot } } -static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { if (!ui->ui_ext[kUITermColors]) { HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp); @@ -738,8 +719,8 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, } } -static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) +void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, + Array info) { if (!ui->ui_ext[kUILinegrid]) { return; @@ -748,10 +729,12 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt UIData *data = ui->data; Array args = data->call_buf; ADD_C(args, INTEGER_OBJ(id)); - MAXSIZE_TEMP_DICT(rgb, 16); - MAXSIZE_TEMP_DICT(cterm, 16); - ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&rgb, rgb_attrs, true))); - ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&cterm, cterm_attrs, false))); + MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE); + MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE); + hlattrs2dict(&rgb, rgb_attrs, true); + hlattrs2dict(&cterm, rgb_attrs, false); + ADD_C(args, DICTIONARY_OBJ(rgb)); + ADD_C(args, DICTIONARY_OBJ(cterm)); if (ui->ui_ext[kUIHlState]) { ADD_C(args, ARRAY_OBJ(info)); @@ -762,7 +745,7 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt push_call(ui, "hl_attr_define", args); } -static void remote_ui_highlight_set(UI *ui, int id) +void remote_ui_highlight_set(UI *ui, int id) { UIData *data = ui->data; Array args = data->call_buf; @@ -771,13 +754,14 @@ static void remote_ui_highlight_set(UI *ui, int id) return; } data->hl_id = id; - MAXSIZE_TEMP_DICT(dict, 16); - ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb))); + MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE); + hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb); + ADD_C(args, DICTIONARY_OBJ(dict)); push_call(ui, "highlight_set", args); } /// "true" cursor used only for input focus -static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) +void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) { if (ui->ui_ext[kUILinegrid]) { UIData *data = ui->data; @@ -795,7 +779,7 @@ static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Intege } /// emulated cursor used both for drawing and for input focus -static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) +void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) { UIData *data = ui->data; if (data->client_row == row && data->client_col == col) { @@ -809,7 +793,7 @@ static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) push_call(ui, "cursor_goto", args); } -static void remote_ui_put(UI *ui, const char *cell) +void remote_ui_put(UI *ui, const char *cell) { UIData *data = ui->data; data->client_col++; @@ -818,9 +802,9 @@ static void remote_ui_put(UI *ui, const char *cell) push_call(ui, "put", args); } -static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) +void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { @@ -841,7 +825,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc for (size_t i = 0; i < ncells; i++) { repeat++; if (i == ncells - 1 || attrs[i] != attrs[i + 1] - || STRCMP(chunk[i], chunk[i + 1])) { + || strcmp(chunk[i], chunk[i + 1]) != 0) { if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) { // close to overflowing the redraw buffer. finish this event, // flush, and start a new "grid_line" event at the current position. @@ -912,7 +896,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc /// /// This might happen multiple times before the actual ui_flush, if the /// total redraw size is large! -static void remote_ui_flush_buf(UI *ui) +void remote_ui_flush_buf(UI *ui) { UIData *data = ui->data; if (!data->nevents_pos) { @@ -939,7 +923,7 @@ static void remote_ui_flush_buf(UI *ui) /// /// Clients can know this happened by a final "flush" event at the end of the /// "redraw" batch. -static void remote_ui_flush(UI *ui) +void remote_ui_flush(UI *ui) { UIData *data = ui->data; if (data->nevents > 0 || data->flushed_events) { @@ -952,65 +936,63 @@ static void remote_ui_flush(UI *ui) } } -static Array translate_contents(UI *ui, Array contents) +static Array translate_contents(UI *ui, Array contents, Arena *arena) { - Array new_contents = ARRAY_DICT_INIT; + Array new_contents = arena_array(arena, contents.size); for (size_t i = 0; i < contents.size; i++) { Array item = contents.items[i].data.array; - Array new_item = ARRAY_DICT_INIT; + Array new_item = arena_array(arena, 2); int attr = (int)item.items[0].data.integer; if (attr) { - Dictionary rgb_attrs = hlattrs2dict(NULL, syn_attr2entry(attr), ui->rgb); + Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE); + hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb); ADD(new_item, DICTIONARY_OBJ(rgb_attrs)); } else { ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); } - ADD(new_item, copy_object(item.items[1])); + ADD(new_item, item.items[1]); ADD(new_contents, ARRAY_OBJ(new_item)); } return new_contents; } -static Array translate_firstarg(UI *ui, Array args) +static Array translate_firstarg(UI *ui, Array args, Arena *arena) { - Array new_args = ARRAY_DICT_INIT; + Array new_args = arena_array(arena, args.size); Array contents = args.items[0].data.array; - ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents))); + ADD_C(new_args, ARRAY_OBJ(translate_contents(ui, contents, arena))); for (size_t i = 1; i < args.size; i++) { - ADD(new_args, copy_object(args.items[i])); + ADD(new_args, args.items[i]); } return new_args; } -static void remote_ui_event(UI *ui, char *name, Array args) +void remote_ui_event(UI *ui, char *name, Array args) { + Arena arena = ARENA_EMPTY; UIData *data = ui->data; if (!ui->ui_ext[kUILinegrid]) { // the representation of highlights in cmdline changed, translate back // never consumes args if (strequal(name, "cmdline_show")) { - Array new_args = translate_firstarg(ui, args); + Array new_args = translate_firstarg(ui, args, &arena); push_call(ui, name, new_args); - api_free_array(new_args); - return; + goto free_ret; } else if (strequal(name, "cmdline_block_show")) { Array new_args = data->call_buf; Array block = args.items[0].data.array; - Array new_block = ARRAY_DICT_INIT; + Array new_block = arena_array(&arena, block.size); for (size_t i = 0; i < block.size; i++) { - ADD(new_block, - ARRAY_OBJ(translate_contents(ui, block.items[i].data.array))); + ADD_C(new_block, ARRAY_OBJ(translate_contents(ui, block.items[i].data.array, &arena))); } ADD_C(new_args, ARRAY_OBJ(new_block)); push_call(ui, name, new_args); - api_free_array(new_block); - return; + goto free_ret; } else if (strequal(name, "cmdline_block_append")) { - Array new_args = translate_firstarg(ui, args); + Array new_args = translate_firstarg(ui, args, &arena); push_call(ui, name, new_args); - api_free_array(new_args); - return; + goto free_ret; } } @@ -1022,19 +1004,18 @@ static void remote_ui_event(UI *ui, char *name, Array args) if (data->wildmenu_active) { Array new_args = data->call_buf; Array items = args.items[0].data.array; - Array new_items = ARRAY_DICT_INIT; + Array new_items = arena_array(&arena, items.size); for (size_t i = 0; i < items.size; i++) { - ADD(new_items, copy_object(items.items[i].data.array.items[0])); + ADD_C(new_items, items.items[i].data.array.items[0]); } ADD_C(new_args, ARRAY_OBJ(new_items)); push_call(ui, "wildmenu_show", new_args); - api_free_array(new_items); if (args.items[1].data.integer != -1) { Array new_args2 = data->call_buf; ADD_C(new_args2, args.items[1]); push_call(ui, "wildmenu_select", new_args2); } - return; + goto free_ret; } } else if (strequal(name, "popupmenu_select")) { if (data->wildmenu_active) { @@ -1048,9 +1029,13 @@ static void remote_ui_event(UI *ui, char *name, Array args) } push_call(ui, name, args); + return; + +free_ret: + arena_mem_free(arena_finish(&arena)); } -static void remote_ui_inspect(UI *ui, Dictionary *info) +void remote_ui_inspect(UI *ui, Dictionary *info) { UIData *data = ui->data; PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id)); diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h index bc70406acb..b3fe0fa2bb 100644 --- a/src/nvim/api/ui.h +++ b/src/nvim/api/ui.h @@ -5,8 +5,10 @@ #include "nvim/api/private/defs.h" #include "nvim/map.h" +#include "nvim/ui.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.h.generated.h" +# include "ui_events_remote.h.generated.h" #endif #endif // NVIM_API_UI_H diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 8b7e01e1c3..a08e8dbfeb 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -31,15 +31,15 @@ void visual_bell(void) void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void suspend(void) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3); void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); void screenshot(String path) - FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(7); void option_set(String name, Object value) - FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(4); // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; @@ -69,14 +69,13 @@ void scroll(Integer count) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; // Second revision of the grid protocol, used with ext_linegrid ui option -void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, + Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; -void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void hl_group_set(String name, Integer id) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(6) FUNC_API_CLIENT_IGNORE; void grid_resize(Integer grid, Integer width, Integer height) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) @@ -85,8 +84,8 @@ void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; -void grid_scroll(Integer grid, Integer top, Integer bot, - Integer left, Integer right, Integer rows, Integer cols) +void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, + Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -94,20 +93,15 @@ void grid_destroy(Integer grid) // For performance and simplicity, we use the dense screen representation // in internal code, such as compositor and TUI. The remote_ui module will // translate this in to the public grid_line format. -void raw_line(Integer grid, Integer row, Integer startcol, - Integer endcol, Integer clearcol, Integer clearattr, - LineFlags flags, const schar_T *chunk, const sattr_T *attrs) +void raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, + Integer clearattr, LineFlags flags, const schar_T *chunk, const sattr_T *attrs) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; -void event(char *name, Array args) - FUNC_API_NOEXPORT; - -void win_pos(Integer grid, Window win, Integer startrow, - Integer startcol, Integer width, Integer height) +void win_pos(Integer grid, Window win, Integer startrow, Integer startcol, Integer width, + Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, - Float anchor_row, Float anchor_col, Boolean focusable, - Integer zindex) +void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, Float anchor_row, + Float anchor_col, Boolean focusable, Integer zindex) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_external_pos(Integer grid, Window win) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -115,32 +109,29 @@ void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_close(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE; -void win_viewport(Integer grid, Window win, Integer topline, - Integer botline, Integer curline, Integer curcol, - Integer line_count) - FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY; +void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline, + Integer curcol, Integer line_count) + FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE; -void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, - Integer row, Integer col) +void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; -void popupmenu_show(Array items, Integer selected, - Integer row, Integer col, Integer grid) +void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void popupmenu_select(Integer selected) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void tabline_update(Tabpage current, Array tabs, - Buffer current_buffer, Array buffers) +void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array buffers) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void cmdline_show(Array content, Integer pos, String firstc, String prompt, - Integer indent, Integer level) +void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent, + Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_pos(Integer pos, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; @@ -156,11 +147,11 @@ void cmdline_block_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_show(Array items) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_select(Integer selected) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_hide(void) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void msg_show(String kind, Array content, Boolean replace_last) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ce91c1b4af..a53b30dd8a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -5,9 +5,13 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/buffer.h" #include "nvim/api/deprecated.h" #include "nvim/api/private/converter.h" @@ -15,58 +19,55 @@ #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/api/window.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/buffer_defs.h" -#include "nvim/charset.h" +#include "nvim/channel.h" #include "nvim/context.h" -#include "nvim/decoration.h" -#include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/grid.h" #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" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os_defs.h" #include "nvim/os/process.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/runtime.h" #include "nvim/state.h" #include "nvim/statusline.h" +#include "nvim/strings.h" +#include "nvim/terminal.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/viml/parser/expressions.h" -#include "nvim/viml/parser/parser.h" #include "nvim/window.h" -#define LINE_BUFFER_SIZE 4096 +#define LINE_BUFFER_MIN_SIZE 4096 #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.c.generated.h" @@ -79,19 +80,17 @@ /// @param[out] err Error details, if any /// @return Highlight definition map /// @see nvim_get_hl_by_id -Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) +Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err) FUNC_API_SINCE(3) { Dictionary result = ARRAY_DICT_INIT; int id = syn_name2id(name.data); if (id == 0) { - api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", - name.data); + api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data); return result; } - result = nvim_get_hl_by_id(id, rgb, err); - return result; + return nvim_get_hl_by_id(id, rgb, arena, err); } /// Gets a highlight definition by id. |hlID()| @@ -100,17 +99,16 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) /// @param[out] err Error details, if any /// @return Highlight definition map /// @see nvim_get_hl_by_name -Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) +Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err) FUNC_API_SINCE(3) { Dictionary dic = ARRAY_DICT_INIT; if (syn_get_final_id((int)hl_id) == 0) { - api_set_error(err, kErrorTypeException, - "Invalid highlight id: %" PRId64, hl_id); + api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id); return dic; } int attrcode = syn_id2attr((int)hl_id); - return hl_get_attr_by_id(attrcode, rgb, err); + return hl_get_attr_by_id(attrcode, rgb, arena, err); } /// Gets a highlight group by name @@ -122,10 +120,10 @@ Integer nvim_get_hl_id_by_name(String name) return syn_check_group(name.data, name.size); } -Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) +Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err) { if (ns_id == 0) { - return get_global_hl_defs(); + return get_global_hl_defs(arena); } abort(); } @@ -163,8 +161,8 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// - nocombine: boolean /// - link: name of another highlight group to link to, see |:hi-link|. /// - default: Don't override existing definition |:hi-default| -/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg| -/// - ctermbg: Sets background of cterm color |highlight-ctermbg| +/// - ctermfg: Sets foreground of cterm color |ctermfg| +/// - ctermbg: Sets background of cterm color |ctermbg| /// - cterm: cterm attribute map, like |highlight-args|. If not set, /// cterm attributes will match those from the attribute map /// documented above. @@ -175,6 +173,10 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, name.size); + if (hl_id == 0) { + api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data); + return; + } int link_id = -1; HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); @@ -184,7 +186,7 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) } /// Set active namespace for highlights. This can be set for a single window, -/// see |nvim_win_set_hl_ns|. +/// see |nvim_win_set_hl_ns()|. /// /// @param ns_id the namespace to use /// @param[out] err Error details, if any @@ -198,13 +200,13 @@ void nvim_set_hl_ns(Integer ns_id, Error *err) ns_hl_global = (NS)ns_id; hl_check_ns(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /// Set active namespace for highlights while redrawing. /// /// This function meant to be called while redrawing, primarily from -/// |nvim_set_decoration_provider| on_win and on_line callbacks, which +/// |nvim_set_decoration_provider()| on_win and on_line callbacks, which /// are allowed to change the namespace during a redraw cycle. /// /// @param ns_id the namespace to activate @@ -227,14 +229,14 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err) /// nvim_feedkeys(). /// /// Example: -/// <pre> +/// <pre>vim /// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) /// :call nvim_feedkeys(key, 'n', v:false) /// </pre> /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| -/// @param escape_ks If true, escape K_SPECIAL bytes in `keys` +/// @param escape_ks If true, escape K_SPECIAL bytes in `keys`. /// This should be false if you already used /// |nvim_replace_termcodes()|, and true otherwise. /// @see feedkeys() @@ -335,9 +337,9 @@ Integer nvim_input(String keys) /// mouse input in a GUI. The deprecated pseudokey form /// ("<LeftMouse><col,row>") of |nvim_input()| has the same limitation. /// -/// @param button Mouse button: one of "left", "right", "middle", "wheel". +/// @param button Mouse button: one of "left", "right", "middle", "wheel", "move". /// @param action For ordinary buttons, one of "press", "drag", "release". -/// For the wheel, one of "up", "down", "left", "right". +/// For the wheel, one of "up", "down", "left", "right". Ignored for "move". /// @param modifier String of modifiers each represented by a single char. /// The same specifiers are used as for a key press, except /// that the "-" separator is optional, so "C-A-", "c-a" @@ -364,6 +366,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri code = KE_RIGHTMOUSE; } else if (strequal(button.data, "wheel")) { code = KE_MOUSEDOWN; + } else if (strequal(button.data, "move")) { + code = KE_MOUSEMOVE; } else { goto error; } @@ -380,7 +384,7 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri } else { goto error; } - } else { + } else if (code != KE_MOUSEMOVE) { if (strequal(action.data, "press")) { // pass } else if (strequal(action.data, "drag")) { @@ -520,7 +524,7 @@ Array nvim__runtime_inspect(void) /// Find files in runtime directories /// -/// 'name' can contain wildcards. For example +/// "name" can contain wildcards. For example /// nvim_get_runtime_file("colors/*.vim", true) will return all color /// scheme files. Always use forward slashes (/) in the search pattern for /// subdirectories regardless of platform. @@ -722,8 +726,11 @@ void nvim_set_vvar(String name, Object value, Error *err) /// text chunk with specified highlight. `hl_group` element /// can be omitted for no highlight. /// @param history if true, add to |message-history|. -/// @param opts Optional parameters. Reserved for future use. -void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) +/// @param opts Optional parameters. +/// - verbose: Message was 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. +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); @@ -731,13 +738,19 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) goto error; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - goto error; + bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err); + + if (verbose) { + verbose_enter(); } msg_multiattr(hl_msg, history ? "echomsg" : "echo", history); + if (verbose) { + verbose_leave(); + verbose_stop(); // flush now + } + if (history) { // history takes ownership return; @@ -961,7 +974,7 @@ fail: /// mode. Note: keypresses are sent raw as they would be to the pty /// master end. For instance, a carriage return is sent /// as a "\r", not as a "\n". |textlock| applies. It is possible -/// to call |nvim_chan_send| directly in the callback however. +/// to call |nvim_chan_send()| directly in the callback however. /// ["input", term, bufnr, data] /// @param[out] err Error details, if any /// @return Channel id, or 0 on error @@ -1009,7 +1022,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) return (Integer)chan->id; } -static void term_write(char *buf, size_t size, void *data) +static void term_write(char *buf, size_t size, void *data) // NOLINT(readability-non-const-parameter) { Channel *chan = data; LuaRef cb = chan->stream.internal.cb; @@ -1291,7 +1304,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event) /// "#rrggbb" hexadecimal string. /// /// Example: -/// <pre> +/// <pre>vim /// :echo nvim_get_color_by_name("Pink") /// :echo nvim_get_color_by_name("#cbcbcb") /// </pre> @@ -1419,10 +1432,10 @@ Dictionary nvim_get_mode(void) /// @param mode Mode short-name ("n", "i", "v", ...) /// @returns Array of |maparg()|-like dictionaries describing mappings. /// The "buffer" key is always zero. -ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) +ArrayOf(Dictionary) nvim_get_keymap(String mode) FUNC_API_SINCE(3) { - return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL); + return keymap_array(mode, NULL); } /// Sets a global |mapping| for the given mode. @@ -1433,12 +1446,12 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. /// /// Example: -/// <pre> +/// <pre>vim /// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) /// </pre> /// /// is equivalent to: -/// <pre> +/// <pre>vim /// nmap <nowait> <Space><NL> <Nop> /// </pre> /// @@ -1449,7 +1462,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map: keys are |:map-arguments|, values are booleans (default /// false). Accepts all |:map-arguments| as keys excluding |<buffer>| but including -/// |noremap| and "desc". Unknown key is an error. +/// |:noremap| and "desc". Unknown key is an error. /// "desc" can be used to give a description to the mapping. /// When called from Lua, also accepts a "callback" key that takes a Lua function to /// call when the mapping is executed. @@ -1479,14 +1492,14 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) /// 1 is the |api-metadata| map (Dictionary). /// /// @returns 2-tuple [{channel-id}, {api-metadata}] -Array nvim_get_api_info(uint64_t channel_id) +Array nvim_get_api_info(uint64_t channel_id, Arena *arena) FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); assert(channel_id <= INT64_MAX); - ADD(rv, INTEGER_OBJ((int64_t)channel_id)); - ADD(rv, DICTIONARY_OBJ(api_metadata())); + ADD_C(rv, INTEGER_OBJ((int64_t)channel_id)); + ADD_C(rv, DICTIONARY_OBJ(api_metadata())); return rv; } @@ -1545,9 +1558,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY { Dictionary info = ARRAY_DICT_INIT; - PUT(info, "name", copy_object(STRING_OBJ(name))); + PUT(info, "name", copy_object(STRING_OBJ(name), NULL)); - version = copy_dictionary(version); + version = copy_dictionary(version, NULL); bool has_major = false; for (size_t i = 0; i < version.size; i++) { if (strequal(version.items[i].key.data, "major")) { @@ -1560,9 +1573,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, } PUT(info, "version", DICTIONARY_OBJ(version)); - PUT(info, "type", copy_object(STRING_OBJ(type))); - PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods))); - PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes))); + PUT(info, "type", copy_object(STRING_OBJ(type), NULL)); + PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods, NULL))); + PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes, NULL))); rpc_set_client_info(channel_id, info); } @@ -1629,11 +1642,11 @@ Array nvim_list_chans(void) /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an /// error occurred, the values from all preceding calls will still be returned. -Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) +Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; - Array results = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); + Array results = arena_array(arena, calls.size); Error nested_error = ERROR_INIT; size_t i; // also used for freeing the variables @@ -1642,21 +1655,21 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays"); - goto validation_error; + goto theend; } Array call = calls.items[i].data.array; if (call.size != 2) { api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays of size 2"); - goto validation_error; + goto theend; } if (call.items[0].type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "Name must be String"); - goto validation_error; + goto theend; } String name = call.items[0].data.string; @@ -1664,7 +1677,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Args must be Array"); - goto validation_error; + goto theend; } Array args = call.items[1].data.array; @@ -1676,29 +1689,32 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) if (ERROR_SET(&nested_error)) { break; } - Object result = handler.fn(channel_id, args, &nested_error); + + Object result = handler.fn(channel_id, args, arena, &nested_error); if (ERROR_SET(&nested_error)) { // error handled after loop break; } - - ADD(results, result); + // TODO(bfredl): wasteful copy. It could be avoided to encoding to msgpack + // directly here. But `result` might become invalid when next api function + // is called in the loop. + ADD_C(results, copy_object(result, arena)); + if (!handler.arena_return) { + api_free_object(result); + } } - ADD(rv, ARRAY_OBJ(results)); + ADD_C(rv, ARRAY_OBJ(results)); if (ERROR_SET(&nested_error)) { - Array errval = ARRAY_DICT_INIT; - ADD(errval, INTEGER_OBJ((Integer)i)); - ADD(errval, INTEGER_OBJ(nested_error.type)); - ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); - ADD(rv, ARRAY_OBJ(errval)); + Array errval = arena_array(arena, 3); + ADD_C(errval, INTEGER_OBJ((Integer)i)); + ADD_C(errval, INTEGER_OBJ(nested_error.type)); + ADD_C(errval, STRING_OBJ(copy_string(cstr_as_string(nested_error.msg), arena))); + ADD_C(rv, ARRAY_OBJ(errval)); } else { - ADD(rv, NIL); + ADD_C(rv, NIL); } - goto theend; -validation_error: - api_free_array(results); theend: api_clear_error(&nested_error); return rv; @@ -1712,17 +1728,21 @@ theend: /// @param to_err true: message is an error (uses `emsg` instead of `msg`) static void write_msg(String message, bool to_err) { - static size_t out_pos = 0, err_pos = 0; - static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE]; + static StringBuilder out_line_buf = KV_INITIAL_VALUE; + static StringBuilder err_line_buf = KV_INITIAL_VALUE; -#define PUSH_CHAR(i, pos, line_buf, msg) \ - if (message.data[i] == NL || (pos) == LINE_BUFFER_SIZE - 1) { \ - (line_buf)[pos] = NUL; \ - msg(line_buf); \ - (pos) = 0; \ +#define PUSH_CHAR(i, line_buf, msg) \ + if (kv_max(line_buf) == 0) { \ + kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \ + } \ + if (message.data[i] == NL) { \ + kv_push(line_buf, NUL); \ + msg(line_buf.items); \ + kv_drop(line_buf, kv_size(line_buf)); \ + kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \ continue; \ } \ - (line_buf)[(pos)++] = message.data[i]; + kv_push(line_buf, message.data[i]); no_wait_return++; for (uint32_t i = 0; i < message.size; i++) { @@ -1730,9 +1750,9 @@ static void write_msg(String message, bool to_err) break; } if (to_err) { - PUSH_CHAR(i, err_pos, err_line_buf, emsg); + PUSH_CHAR(i, err_line_buf, emsg); } else { - PUSH_CHAR(i, out_pos, out_line_buf, msg); + PUSH_CHAR(i, out_line_buf, msg); } } no_wait_return--; @@ -1751,7 +1771,7 @@ static void write_msg(String message, bool to_err) /// @return its argument. Object nvim__id(Object obj) { - return copy_object(obj); + return copy_object(obj, NULL); } /// Returns array given as argument. @@ -1764,7 +1784,7 @@ Object nvim__id(Object obj) /// @return its argument. Array nvim__id_array(Array arr) { - return copy_object(ARRAY_OBJ(arr)).data.array; + return copy_array(arr, NULL); } /// Returns dictionary given as argument. @@ -1777,7 +1797,7 @@ Array nvim__id_array(Array arr) /// @return its argument. Dictionary nvim__id_dictionary(Dictionary dct) { - return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; + return copy_dictionary(dct, NULL); } /// Returns floating-point value given as argument. @@ -1803,6 +1823,7 @@ Dictionary nvim__stats(void) PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip)); PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count)); return rv; } @@ -1813,7 +1834,7 @@ Dictionary nvim__stats(void) /// - "width" Requested width of the UI /// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|) /// - "ext_..." Requested UI extensions, see |ui-option| -/// - "chan" Channel id of remote UI (not present for TUI) +/// - "chan" |channel-id| of remote UI Array nvim_list_uis(void) FUNC_API_SINCE(4) { @@ -1875,7 +1896,7 @@ Object nvim_get_proc(Integer pid, Error *err) api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); return NIL; } -#ifdef WIN32 +#ifdef MSWIN rvobj.data.dictionary = os_proc_info((int)pid); if (rvobj.data.dictionary.size == 0) { // Process not found. return NIL; @@ -1897,19 +1918,20 @@ Object nvim_get_proc(Integer pid, Error *err) return rvobj; } -/// Selects an item in the completion popupmenu. +/// Selects an item in the completion popup menu. /// -/// If |ins-completion| is not active this API call is silently ignored. -/// Useful for an external UI using |ui-popupmenu| to control the popupmenu -/// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to -/// ensure the mapping doesn't end completion mode. +/// If neither |ins-completion| nor |cmdline-completion| popup menu is active +/// this API call is silently ignored. +/// Useful for an external UI using |ui-popupmenu| to control the popup menu with the mouse. +/// Can also be used in a mapping; use <Cmd> |:map-cmd| or a Lua mapping to ensure the mapping +/// doesn't end completion mode. /// -/// @param item Index (zero-based) of the item to select. Value of -1 selects -/// nothing and restores the original text. -/// @param insert Whether the selection should be inserted in the buffer. -/// @param finish Finish the completion and dismiss the popupmenu. Implies -/// `insert`. -/// @param opts Optional parameters. Reserved for future use. +/// @param item Index (zero-based) of the item to select. Value of -1 selects nothing +/// and restores the original text. +/// @param insert For |ins-completion|, whether the selection should be inserted in the buffer. +/// Ignored for |cmdline-completion|. +/// @param finish Finish the completion and dismiss the popup menu. Implies {insert}. +/// @param opts Optional parameters. Reserved for future use. /// @param[out] err Error details, if any void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts, Error *err) @@ -1928,7 +1950,7 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di } /// NB: if your UI doesn't use hlstate, this will not return hlstate first time -Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) +Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, Error *err) { Array ret = ARRAY_DICT_INIT; @@ -1952,13 +1974,14 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) || col < 0 || col >= g->cols) { return ret; } + ret = arena_array(arena, 3); size_t off = g->line_offset[(size_t)row] + (size_t)col; - ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off]))); + ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off]))); int attr = g->attrs[off]; - ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err))); + ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err))); // will not work first time if (!highlight_use_hlstate()) { - ADD(ret, ARRAY_OBJ(hl_inspect(attr))); + ADD_C(ret, ARRAY_OBJ(hl_inspect(attr))); } return ret; } @@ -2040,7 +2063,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) // Marks are from an open buffer it fnum is non zero if (mark->fmark.fnum != 0) { bufnr = mark->fmark.fnum; - filename = (char *)buflist_nr2name(bufnr, true, true); + filename = buflist_nr2name(bufnr, true, true); allocated = true; // Marks comes from shada } else { @@ -2088,7 +2111,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_winbar: (boolean) Evaluate winbar instead of statusline. -/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} +/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid} /// is ignored. Mutually exclusive with {use_winbar}. /// /// @param[out] err Error details, if any. @@ -2096,7 +2119,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - str: (string) Characters that will be displayed on the statusline. /// - width: (number) Display width of the statusline. /// - highlights: Array containing highlight information of the statusline. Only included when -/// the "highlights" key in {opts} is |TRUE|. Each element of the array is a +/// the "highlights" key in {opts} is true. Each element of the array is a /// |Dictionary| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. /// - group: (string) Name of highlight group. @@ -2112,7 +2135,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * bool use_tabline = false; bool highlights = false; - if (str.size < 2 || memcmp(str.data, "%!", 2)) { + if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) { const char *const errmsg = check_stl_option(str.data); if (errmsg) { api_set_error(err, kErrorTypeValidation, "%s", errmsg); @@ -2210,10 +2233,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * buf, sizeof(buf), str.data, - false, + NULL, + 0, fillchar, maxwidth, hltab_ptr, + NULL, NULL); PUT(result, "width", INTEGER_OBJ(width)); @@ -2228,7 +2253,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list - if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) { + if (hltab->start == NULL || (hltab->start - buf) != 0) { Dictionary hl_info = ARRAY_DICT_INIT; grpname = get_default_stl_hl(wp, use_winbar); @@ -2246,7 +2271,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (sp->userhl == 0) { grpname = get_default_stl_hl(wp, use_winbar); } else if (sp->userhl < 0) { - grpname = (char *)syn_id2name(-sp->userhl); + grpname = syn_id2name(-sp->userhl); } else { snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); grpname = user_group; diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index a28bfd2ab9..af1b23b712 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -2,26 +2,31 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <limits.h> -#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vimscript.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" -#include "nvim/ops.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #include "nvim/runtime.h" -#include "nvim/strings.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vimscript.c.generated.h" @@ -137,7 +142,7 @@ Object nvim_eval(String expr, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } @@ -196,7 +201,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } @@ -204,10 +209,10 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) try_start(); typval_T rettv; funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; - funcexe.selfdict = self; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; + funcexe.fe_selfdict = self; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (see above) to capture abort-causing non-exception errors. (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, @@ -304,7 +309,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) } fn = (String) { .data = di->di_tv.vval.v_string, - .size = STRLEN(di->di_tv.vval.v_string), + .size = strlen(di->di_tv.vval.v_string), }; } @@ -532,7 +537,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E kv_drop(ast_conv_stack, 1); } else { if (cur_item.ret_node_p->type == kObjectTypeNil) { - size_t items_size = (size_t)(3 // "type", "start" and "len" + size_t items_size = (size_t)(3 // "type", "start" and "len" // NOLINT(bugprone-misplaced-widening-cast) + (node->children != NULL) // "children" + (node->type == kExprNodeOption || node->type == kExprNodePlainIdentifier) // "scope" diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 6c37df6af8..0ffeac1bff 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1,19 +1,27 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <stdbool.h> -#include <stddef.h> -#include <stdint.h> +#include <string.h> +#include "klib/kvec.h" +#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/drawscreen.h" +#include "nvim/extmark_defs.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/option.h" -#include "nvim/strings.h" +#include "nvim/pos.h" #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/window.h" @@ -47,13 +55,13 @@ /// this should not be used to specify arbitrary WM screen positions. /// /// Example (Lua): window-relative float -/// <pre> +/// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', row=3, col=3, width=12, height=3}) /// </pre> /// /// Example (Lua): buffer-relative float (travels as buffer is scrolled) -/// <pre> +/// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', width=12, height=3, bufpos={100,10}}) /// </pre> @@ -66,6 +74,7 @@ /// - "editor" The global editor grid /// - "win" Window given by the `win` field, or current window. /// - "cursor" Cursor position in current window. +/// - "mouse" Mouse position /// - win: |window-ID| for relative="win". /// - anchor: Decides which corner of the float to place at (row,col): /// - "NW" northwest (default) @@ -106,10 +115,11 @@ /// float where the text should not be edited. Disables /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' -/// is changed to `auto` and 'colorcolumn' is cleared. The -/// end-of-buffer region is hidden by setting `eob` flag of +/// is changed to `auto` and 'colorcolumn' is cleared. +/// 'statuscolumn' is changed to empty. The end-of-buffer +/// region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the -/// |EndOfBuffer| region in 'winhighlight'. +/// |hl-EndOfBuffer| region in 'winhighlight'. /// - border: Style of (optional) window border. This can either be a string /// or an array. The string values are /// - "none": No border (default). @@ -133,7 +143,12 @@ /// will only make vertical borders but not horizontal ones. /// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: -/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. +/// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +/// - title: Title (optional) in window border, String or list. +/// List is [text, highlight] tuples. if is string the default +/// highlight group is `FloatTitle`. +/// - title_pos: Title position must set with title option. +/// value can be of `left` `center` `right` default is left. /// - noautocmd: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. @@ -202,7 +217,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) if (!win_new_float(win, false, fconfig, err)) { return; } - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } else { win_config_float(win, fconfig); win->w_pos_changed = true; @@ -263,7 +278,7 @@ Dictionary nvim_win_get_config(Window window, Error *err) String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T)); int hi_id = config->border_hl_ids[i]; - char *hi_name = (char *)syn_id2name(hi_id); + char *hi_name = syn_id2name(hi_id); if (hi_name[0]) { ADD(tuple, STRING_OBJ(s)); ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name))); @@ -273,6 +288,29 @@ Dictionary nvim_win_get_config(Window window, Error *err) } } PUT(rv, "border", ARRAY_OBJ(border)); + if (config->title) { + Array titles = ARRAY_DICT_INIT; + VirtText title_datas = config->title_chunks; + for (size_t i = 0; i < title_datas.size; i++) { + Array tuple = ARRAY_DICT_INIT; + ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text)); + if (title_datas.items[i].hl_id > 0) { + ADD(tuple, + STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id)))); + } + ADD(titles, ARRAY_OBJ(tuple)); + } + PUT(rv, "title", ARRAY_OBJ(titles)); + char *title_pos; + if (config->title_pos == kAlignLeft) { + title_pos = "left"; + } else if (config->title_pos == kAlignCenter) { + title_pos = "center"; + } else { + title_pos = "right"; + } + PUT(rv, "title_pos", CSTR_TO_OBJ(title_pos)); + } } } @@ -312,6 +350,8 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeWindow; } else if (striequal(str, "cursor")) { *out = kFloatRelativeCursor; + } else if (striequal(str, "mouse")) { + *out = kFloatRelativeMouse; } else { return false; } @@ -330,7 +370,74 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out) return true; } -static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err) +{ + if (!parse_title_pos(title_pos, fconfig, err)) { + return; + } + + if (title.type == kObjectTypeString) { + if (title.data.string.size == 0) { + fconfig->title = false; + return; + } + int hl_id = syn_check_group(S_LEN("FloatTitle")); + kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data), + .hl_id = hl_id })); + fconfig->title_width = (int)mb_string2cells(title.data.string.data); + fconfig->title = true; + return; + } + + if (title.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "title must be string or array"); + return; + } + + if (title.data.array.size == 0) { + api_set_error(err, kErrorTypeValidation, "title cannot be an empty array"); + return; + } + + fconfig->title_width = 0; + fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width); + + fconfig->title = true; +} + +static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err) +{ + if (!HAS_KEY(title_pos)) { + fconfig->title_pos = kAlignLeft; + return true; + } + + if (title_pos.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "title_pos must be string"); + return false; + } + + if (title_pos.data.string.size == 0) { + fconfig->title_pos = kAlignLeft; + return true; + } + + char *pos = title_pos.data.string.data; + + if (strequal(pos, "left")) { + fconfig->title_pos = kAlignLeft; + } else if (strequal(pos, "center")) { + fconfig->title_pos = kAlignCenter; + } else if (strequal(pos, "right")) { + fconfig->title_pos = kAlignRight; + } else { + api_set_error(err, kErrorTypeValidation, "invalid title_pos value"); + return false; + } + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { struct { const char *name; @@ -414,6 +521,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) String str = style.data.string; if (str.size == 0 || strequal(str.data, "none")) { fconfig->border = false; + // title does not work with border equal none + fconfig->title = false; return; } for (size_t i = 0; defaults[i].name; i++) { @@ -603,6 +712,29 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, return false; } + if (HAS_KEY(config->title_pos)) { + if (!HAS_KEY(config->title)) { + api_set_error(err, kErrorTypeException, "title_pos requires title to be set"); + return false; + } + } + + if (HAS_KEY(config->title)) { + // title only work with border + if (!HAS_KEY(config->border) && !fconfig->border) { + api_set_error(err, kErrorTypeException, "title requires border to be set"); + return false; + } + + if (fconfig->title) { + clear_virttext(&fconfig->title_chunks); + } + parse_border_title(config->title, config->title_pos, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } + if (HAS_KEY(config->border)) { parse_border_style(config->border, fconfig, err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 580dfd8639..e2c234ab29 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -10,16 +10,18 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/window.h" #include "nvim/ascii.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" +#include "nvim/eval/window.h" #include "nvim/ex_docmd.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memline_defs.h" #include "nvim/move.h" -#include "nvim/option.h" -#include "nvim/syntax.h" -#include "nvim/vim.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/window.h" /// Gets the current buffer in a window @@ -51,7 +53,9 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) win_set_buf(window, buffer, false, err); } -/// Gets the (1,0)-indexed cursor position in the window. |api-indexing| +/// Gets the (1,0)-indexed, buffer-relative cursor position for a given window +/// (different windows showing the same buffer have independent cursor +/// positions). |api-indexing| /// /// @param window Window handle, or 0 for current window /// @param[out] err Error details, if any @@ -115,10 +119,15 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // Make sure we stick in this column. win->w_set_curswant = true; - // make sure cursor is in visible range even if win != curwin - update_topline_win(win); + // make sure cursor is in visible range and + // cursorcolumn and cursorline are updated even if win != curwin + switchwin_T switchwin; + switch_win(&switchwin, win, NULL, true); + update_topline(curwin); + validate_cursor(); + restore_win(&switchwin, true); - redraw_later(win, VALID); + redraw_later(win, UPD_VALID); win->w_redr_status = true; } @@ -160,9 +169,11 @@ void nvim_win_set_height(Window window, Integer height, Error *err) win_T *savewin = curwin; curwin = win; + curbuf = curwin->w_buffer; try_start(); win_setheight((int)height); curwin = savewin; + curbuf = curwin->w_buffer; try_end(err); } @@ -205,9 +216,11 @@ void nvim_win_set_width(Window window, Integer width, Error *err) win_T *savewin = curwin; curwin = win; + curbuf = curwin->w_buffer; try_start(); win_setwidth((int)width); curwin = savewin; + curbuf = curwin->w_buffer; try_end(err); } @@ -340,7 +353,7 @@ Boolean nvim_win_is_valid(Window window) /// /// Like |:hide| the buffer becomes hidden unless another window is editing it, /// or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or -/// |nvim_win_close|, which will close the buffer. +/// |nvim_win_close()|, which will close the buffer. /// /// @param window Window handle, or 0 for current window /// @param[out] err Error details, if any @@ -356,11 +369,16 @@ void nvim_win_hide(Window window, Error *err) tabpage_T *tabpage = win_find_tabpage(win); TryState tstate; try_enter(&tstate); - if (tabpage == curtab) { + + // 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); } @@ -430,7 +448,7 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) /// Set highlight namespace for a window. This will use highlights defined in /// this namespace, but fall back to global highlights (ns=0) when missing. /// -/// This takes predecence over the 'winhighlight' option. +/// This takes precedence over the 'winhighlight' option. /// /// @param ns_id the namespace to use /// @param[out] err Error details, if any @@ -449,5 +467,5 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) win->w_ns_hl = (NS)ns_id; win->w_hl_needs_update = true; - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } |