diff options
Diffstat (limited to 'src/nvim/api')
| -rw-r--r-- | src/nvim/api/buffer.c | 199 | ||||
| -rw-r--r-- | src/nvim/api/private/defs.h | 42 | ||||
| -rw-r--r-- | src/nvim/api/private/dispatch.c | 3 | ||||
| -rw-r--r-- | src/nvim/api/private/handle.c | 3 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.c | 365 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.h | 26 | ||||
| -rw-r--r-- | src/nvim/api/tabpage.c | 21 | ||||
| -rw-r--r-- | src/nvim/api/ui.c | 291 | ||||
| -rw-r--r-- | src/nvim/api/ui_events.in.h | 99 | ||||
| -rw-r--r-- | src/nvim/api/vim.c | 970 | ||||
| -rw-r--r-- | src/nvim/api/window.c | 59 |
11 files changed, 1531 insertions, 547 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 26f9a6f592..6be981a18e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -1,3 +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 + // Much of this code was adapted from 'if_py_both.h' from the original // vim source #include <stdbool.h> @@ -32,7 +35,7 @@ /// @param[out] err Error details, if any /// @return Line count Integer nvim_buf_line_count(Buffer buffer, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -62,7 +65,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) index = convert_index(index); Array slice = nvim_buf_get_lines(0, buffer, index, index+1, true, err); - if (!err->set && slice.size) { + if (!ERROR_SET(err) && slice.size) { rv = slice.items[0].data.string; } @@ -154,7 +157,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Integer end, Boolean strict_indexing, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -168,7 +171,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, end = normalize_index(buf, end, &oob); if (strict_indexing && oob) { - api_set_error(err, Validation, _("Index out of bounds")); + api_set_error(err, kErrorTypeValidation, "Index out of bounds"); return rv; } @@ -183,16 +186,16 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, for (size_t i = 0; i < rv.size; i++) { int64_t lnum = start + (int64_t)i; - if (lnum > LONG_MAX) { - api_set_error(err, Validation, _("Line index is too high")); + if (lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); goto end; } - const char *bufstr = (char *) ml_get_buf(buf, (linenr_T) lnum, false); + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); Object str = STRING_OBJ(cstr_to_string(bufstr)); // Vim represents NULs as NLs, but this may confuse clients. - if (channel_id != INTERNAL_CALL) { + if (channel_id != VIML_INTERNAL_CALL) { strchrsub(str.data.string.data, '\n', '\0'); } @@ -200,7 +203,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, } end: - if (err->set) { + if (ERROR_SET(err)) { for (size_t i = 0; i < rv.size; i++) { xfree(rv.items[i].data.string.data); } @@ -267,7 +270,7 @@ void nvim_buf_set_lines(uint64_t channel_id, Boolean strict_indexing, ArrayOf(String) replacement, // NOLINT Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -280,18 +283,36 @@ void nvim_buf_set_lines(uint64_t channel_id, end = normalize_index(buf, end, &oob); if (strict_indexing && oob) { - api_set_error(err, Validation, _("Index out of bounds")); + api_set_error(err, kErrorTypeValidation, "Index out of bounds"); return; } if (start > end) { api_set_error(err, - Validation, - _("Argument \"start\" is higher than \"end\"")); + kErrorTypeValidation, + "Argument \"start\" is higher than \"end\""); return; } + for (size_t i = 0; i < replacement.size; i++) { + if (replacement.items[i].type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "All items in the replacement array must be strings"); + return; + } + // Disallow newlines in the middle of the line. + if (channel_id != VIML_INTERNAL_CALL) { + const String l = replacement.items[i].data.string; + if (memchr(l.data, NL, l.size)) { + api_set_error(err, kErrorTypeValidation, + "String cannot contain newlines"); + return; + } + } + } + win_T *save_curwin = NULL; tabpage_T *save_curtab = NULL; size_t new_len = replacement.size; @@ -300,34 +321,20 @@ void nvim_buf_set_lines(uint64_t channel_id, char **lines = (new_len != 0) ? xcalloc(new_len, sizeof(char *)) : NULL; for (size_t i = 0; i < new_len; i++) { - if (replacement.items[i].type != kObjectTypeString) { - api_set_error(err, - Validation, - _("All items in the replacement array must be strings")); - goto end; - } - - String l = replacement.items[i].data.string; + const String l = replacement.items[i].data.string; - // Fill lines[i] with l's contents. Disallow newlines in the middle of a - // line and convert NULs to newlines to avoid truncation. - lines[i] = xmallocz(l.size); - for (size_t j = 0; j < l.size; j++) { - if (l.data[j] == '\n' && channel_id != INTERNAL_CALL) { - api_set_error(err, Exception, _("string cannot contain newlines")); - new_len = i + 1; - goto end; - } - lines[i][j] = (char) (l.data[j] == '\0' ? '\n' : l.data[j]); - } + // Fill lines[i] with l's contents. Convert NULs to newlines as required by + // NL-used-for-NUL. + lines[i] = xmemdupz(l.data, l.size); + memchrsub(lines[i], NUL, NL, l.size); } try_start(); - bufref_T save_curbuf = { NULL, 0 }; + bufref_T save_curbuf = { NULL, 0, 0 }; switch_to_win_for_buf(buf, &save_curwin, &save_curtab, &save_curbuf); if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) { - api_set_error(err, Exception, _("Failed to save undo information")); + api_set_error(err, kErrorTypeException, "Failed to save undo information"); goto end; } @@ -337,7 +344,7 @@ void nvim_buf_set_lines(uint64_t channel_id, size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0; for (size_t i = 0; i < to_delete; i++) { if (ml_delete((linenr_T)start, false) == FAIL) { - api_set_error(err, Exception, _("Failed to delete line")); + api_set_error(err, kErrorTypeException, "Failed to delete line"); goto end; } } @@ -353,13 +360,13 @@ void nvim_buf_set_lines(uint64_t channel_id, for (size_t i = 0; i < to_replace; i++) { int64_t lnum = start + (int64_t)i; - if (lnum > LONG_MAX) { - api_set_error(err, Validation, _("Index value is too high")); + if (lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Index value is too high"); goto end; } if (ml_replace((linenr_T)lnum, (char_u *)lines[i], false) == FAIL) { - api_set_error(err, Exception, _("Failed to replace line")); + api_set_error(err, kErrorTypeException, "Failed to replace line"); goto end; } // Mark lines that haven't been passed to the buffer as they need @@ -371,13 +378,13 @@ void nvim_buf_set_lines(uint64_t channel_id, for (size_t i = to_replace; i < new_len; i++) { int64_t lnum = start + (int64_t)i - 1; - if (lnum > LONG_MAX) { - api_set_error(err, Validation, _("Index value is too high")); + if (lnum >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Index value is too high"); goto end; } if (ml_append((linenr_T)lnum, (char_u *)lines[i], 0, false) == FAIL) { - api_set_error(err, Exception, _("Failed to insert line")); + api_set_error(err, kErrorTypeException, "Failed to insert line"); goto end; } @@ -392,7 +399,11 @@ void nvim_buf_set_lines(uint64_t channel_id, // Only adjust marks if we managed to switch to a window that holds // the buffer, otherwise line numbers will be invalid. if (save_curbuf.br_buf == NULL) { - mark_adjust((linenr_T)start, (linenr_T)(end - 1), MAXLNUM, extra); + mark_adjust((linenr_T)start, + (linenr_T)(end - 1), + MAXLNUM, + (long)extra, + false); } changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra); @@ -418,7 +429,7 @@ end: /// @param[out] err Error details, if any /// @return Variable value Object nvim_buf_get_var(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -432,10 +443,11 @@ Object nvim_buf_get_var(Buffer buffer, String name, Error *err) /// Gets a changed tick of a buffer /// /// @param[in] buffer Buffer handle. +/// @param[out] err Error details, if any /// /// @return `b:changedtick` value. Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) - FUNC_API_SINCE(2) + FUNC_API_SINCE(2) { const buf_T *const buf = find_buffer_by_handle(buffer, err); @@ -446,6 +458,26 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) return buf->b_changedtick; } +/// Gets a list of dictionaries describing buffer-local mappings. +/// The "buffer" key in the returned dictionary reflects the buffer +/// handle where the mapping is present. +/// +/// @param mode Mode short-name ("n", "i", "v", ...) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @returns Array of maparg()-like dictionaries describing mappings +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); + + if (!buf) { + return (Array)ARRAY_DICT_INIT; + } + + return keymap_array(mode, buf); +} + /// Sets a buffer-scoped (b:) variable /// /// @param buffer Buffer handle @@ -453,7 +485,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// @param value Variable value /// @param[out] err Error details, if any void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -470,7 +502,7 @@ void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) /// @param name Variable name /// @param[out] err Error details, if any void nvim_buf_del_var(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -531,7 +563,7 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) /// @param[out] err Error details, if any /// @return Option value Object nvim_buf_get_option(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -550,7 +582,7 @@ Object nvim_buf_get_option(Buffer buffer, String name, Error *err) /// @param value Option value /// @param[out] err Error details, if any void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -563,11 +595,15 @@ void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) /// Gets the buffer number /// +/// @deprecated The buffer number now is equal to the object id, +/// so there is no need to use this function. +/// /// @param buffer Buffer handle /// @param[out] err Error details, if any /// @return Buffer number Integer nvim_buf_get_number(Buffer buffer, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) + FUNC_API_DEPRECATED_SINCE(2) { Integer rv = 0; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -585,7 +621,7 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) /// @param[out] err Error details, if any /// @return Buffer name String nvim_buf_get_name(Buffer buffer, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { String rv = STRING_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -603,7 +639,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err) /// @param name Buffer name /// @param[out] err Error details, if any void nvim_buf_set_name(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -624,7 +660,7 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) } if (ren_ret == FAIL) { - api_set_error(err, Exception, _("Failed to rename buffer")); + api_set_error(err, kErrorTypeException, "Failed to rename buffer"); } } @@ -633,10 +669,12 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) /// @param buffer Buffer handle /// @return true if the buffer is valid, false otherwise Boolean nvim_buf_is_valid(Buffer buffer) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Error stub = ERROR_INIT; - return find_buffer_by_handle(buffer, &stub) != NULL; + Boolean ret = find_buffer_by_handle(buffer, &stub) != NULL; + api_clear_error(&stub); + return ret; } /// Inserts a sequence of lines to a buffer at a certain index @@ -665,7 +703,7 @@ void buffer_insert(Buffer buffer, /// @param[out] err Error details, if any /// @return (row, col) tuple ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -675,7 +713,8 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) } if (name.size != 1) { - api_set_error(err, Validation, _("Mark name must be a single character")); + api_set_error(err, kErrorTypeValidation, + "Mark name must be a single character"); return rv; } @@ -693,7 +732,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) } if (posp == NULL) { - api_set_error(err, Validation, _("Invalid mark name")); + api_set_error(err, kErrorTypeValidation, "Invalid mark name"); return rv; } @@ -705,31 +744,31 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// Adds a highlight to buffer. /// -/// This can be used for plugins which dynamically generate highlights to a -/// buffer (like a semantic highlighter or linter). The function adds a single +/// Useful for plugins that dynamically generate highlights to a buffer +/// (like a semantic highlighter or linter). The function adds a single /// highlight to a buffer. Unlike matchaddpos() highlights follow changes to /// line numbering (as lines are inserted/removed above the highlighted line), /// like signs and marks do. /// -/// "src_id" is useful for batch deletion/updating of a set of highlights. When -/// called with src_id = 0, an unique source id is generated and returned. -/// Succesive calls can pass in it as "src_id" to add new highlights to the same -/// source group. All highlights in the same group can then be cleared with -/// nvim_buf_clear_highlight. If the highlight never will be manually deleted -/// pass in -1 for "src_id". +/// `src_id` is useful for batch deletion/updating of a set of highlights. When +/// called with `src_id = 0`, an unique source id is generated and returned. +/// Successive calls can pass that `src_id` to associate new highlights with +/// the same source group. All highlights in the same group can be cleared +/// with `nvim_buf_clear_highlight`. If the highlight never will be manually +/// deleted, pass `src_id = -1`. /// -/// If "hl_group" is the empty string no highlight is added, but a new src_id +/// If `hl_group` is the empty string no highlight is added, but a new `src_id` /// is still returned. This is useful for an external plugin to synchrounously -/// request an unique src_id at initialization, and later asynchronously add and -/// clear highlights in response to buffer changes. +/// request an unique `src_id` at initialization, and later asynchronously add +/// and clear highlights in response to buffer changes. /// /// @param buffer Buffer handle /// @param src_id Source group to use or 0 to use a new group, /// or -1 for ungrouped highlight /// @param hl_group Name of the highlight group to use -/// @param line Line to highlight -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, +/// @param line Line to highlight (zero-indexed) +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, /// or -1 to highlight to end of line /// @param[out] err Error details, if any /// @return The src_id that was used @@ -740,7 +779,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer col_start, Integer col_end, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -748,18 +787,22 @@ Integer nvim_buf_add_highlight(Buffer buffer, } if (line < 0 || line >= MAXLNUM) { - api_set_error(err, Validation, _("Line number outside range")); + api_set_error(err, kErrorTypeValidation, "Line number outside range"); return 0; } if (col_start < 0 || col_start > MAXCOL) { - api_set_error(err, Validation, _("Column value outside range")); + api_set_error(err, kErrorTypeValidation, "Column value outside range"); return 0; } if (col_end < 0 || col_end > MAXCOL) { col_end = MAXCOL; } - int hlg_id = syn_name2id((char_u *)(hl_group.data ? hl_group.data : "")); + int hlg_id = 0; + if (hl_group.size > 0) { + hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + } + src_id = bufhl_add_hl(buf, (int)src_id, hlg_id, (linenr_T)line+1, (colnr_T)col_start+1, (colnr_T)col_end); return src_id; @@ -767,7 +810,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, /// Clears highlights from a given source group and a range of lines /// -/// To clear a source group in the entire buffer, pass in 1 and -1 to +/// To clear a source group in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. /// /// @param buffer Buffer handle @@ -781,7 +824,7 @@ void nvim_buf_clear_highlight(Buffer buffer, Integer line_start, Integer line_end, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -789,7 +832,7 @@ void nvim_buf_clear_highlight(Buffer buffer, } if (line_start < 0 || line_start >= MAXLNUM) { - api_set_error(err, Validation, _("Line number outside range")); + api_set_error(err, kErrorTypeValidation, "Line number outside range"); return; } if (line_end < 0 || line_end > MAXLNUM) { diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 223aab09dc..390b7e8363 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -5,12 +5,16 @@ #include <stdbool.h> #include <string.h> +#include "nvim/func_attr.h" + #define ARRAY_DICT_INIT {.size = 0, .capacity = 0, .items = NULL} #define STRING_INIT {.data = NULL, .size = 0} #define OBJECT_INIT { .type = kObjectTypeNil } -#define ERROR_INIT { .set = false } +#define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } #define REMOTE_TYPE(type) typedef handle_T type +#define ERROR_SET(e) ((e)->type != kErrorTypeNone) + #ifdef INCLUDE_GENERATED_DECLARATIONS # define ArrayOf(...) Array # define DictionaryOf(...) Dictionary @@ -20,6 +24,7 @@ typedef int handle_T; // Basic types typedef enum { + kErrorTypeNone = -1, kErrorTypeException, kErrorTypeValidation } ErrorType; @@ -33,13 +38,31 @@ typedef enum { /// Used as the message ID of notifications. #define NO_RESPONSE UINT64_MAX -/// Used as channel_id when the call is local. -#define INTERNAL_CALL UINT64_MAX +/// Mask for all internal calls +#define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1)) + +/// Internal call from VimL code +#define VIML_INTERNAL_CALL INTERNAL_CALL_MASK + +/// Internal call from lua code +#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) + +static inline bool is_internal_call(const uint64_t channel_id) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; + +/// Check whether call is internal +/// +/// @param[in] channel_id Channel id. +/// +/// @return true if channel_id refers to internal channel. +static inline bool is_internal_call(const uint64_t channel_id) +{ + return !!(channel_id & INTERNAL_CALL_MASK); +} typedef struct { ErrorType type; - char msg[1024]; - bool set; + char *msg; } Error; typedef bool Boolean; @@ -76,16 +99,17 @@ typedef struct { } Dictionary; typedef enum { - kObjectTypeBuffer, - kObjectTypeWindow, - kObjectTypeTabpage, - kObjectTypeNil, + kObjectTypeNil = 0, kObjectTypeBoolean, kObjectTypeInteger, kObjectTypeFloat, kObjectTypeString, kObjectTypeArray, kObjectTypeDictionary, + // EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT + kObjectTypeBuffer, + kObjectTypeWindow, + kObjectTypeTabpage, } ObjectType; struct object { diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index 9b3bcc380a..f8eebcdb10 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -1,3 +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 <inttypes.h> #include <stdbool.h> #include <stdint.h> diff --git a/src/nvim/api/private/handle.c b/src/nvim/api/private/handle.c index acb0fb332a..eb96192af2 100644 --- a/src/nvim/api/private/handle.c +++ b/src/nvim/api/private/handle.c @@ -1,3 +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 <assert.h> #include <stdint.h> diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index fe15b28041..12a4279dd7 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1,3 +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 <assert.h> #include <inttypes.h> #include <stdbool.h> @@ -9,6 +12,7 @@ #include "nvim/api/private/handle.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/ascii.h" +#include "nvim/assert.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/window.h" @@ -21,6 +25,8 @@ #include "nvim/option_defs.h" #include "nvim/version.h" #include "nvim/lib/kvec.h" +#include "nvim/getchar.h" +#include "nvim/ui.h" /// Helper structure for vim_to_object typedef struct { @@ -30,9 +36,74 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.c.generated.h" # include "api/private/funcs_metadata.generated.h" +# include "api/private/ui_events_metadata.generated.h" #endif +/// Start block that may cause VimL exceptions while evaluating another code +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +/// +/// @param[out] tstate Location where try state should be saved. +void try_enter(TryState *const tstate) +{ + // TODO(ZyX-I): Check whether try_enter()/try_leave() may use + // enter_cleanup()/leave_cleanup(). Or + // save_dbg_stuff()/restore_dbg_stuff(). + *tstate = (TryState) { + .current_exception = current_exception, + .msg_list = (const struct msglist *const *)msg_list, + .private_msg_list = NULL, + .trylevel = trylevel, + .got_int = got_int, + .need_rethrow = need_rethrow, + .did_emsg = did_emsg, + }; + msg_list = &tstate->private_msg_list; + current_exception = NULL; + trylevel = 1; + got_int = false; + need_rethrow = false; + did_emsg = false; +} + +/// End try block, set the error message if any and restore previous state +/// +/// @warning Return is consistent with most functions (false on error), not with +/// try_end (true on error). +/// +/// @param[in] tstate Previous state to restore. +/// @param[out] err Location where error should be saved. +/// +/// @return false if error occurred, true otherwise. +bool try_leave(const TryState *const tstate, Error *const err) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const bool ret = !try_end(err); + assert(trylevel == 0); + assert(!need_rethrow); + assert(!got_int); + assert(!did_emsg); + assert(msg_list == &tstate->private_msg_list); + assert(*msg_list == NULL); + assert(current_exception == NULL); + msg_list = (struct msglist **)tstate->msg_list; + current_exception = tstate->current_exception; + trylevel = tstate->trylevel; + got_int = tstate->got_int; + need_rethrow = tstate->need_rethrow; + did_emsg = tstate->did_emsg; + return ret; +} + /// Start block that may cause vimscript exceptions +/// +/// Each try_start() call should be mirrored by try_end() call. +/// +/// To be used as a replacement of `:try … catch … endtry` in C code, in cases +/// when error flag could not already be set. If there may be pending error +/// state at the time try_start() is executed which needs to be preserved, +/// try_enter()/try_leave() pair should be used instead. void try_start(void) { ++trylevel; @@ -45,7 +116,9 @@ void try_start(void) /// @return true if an error occurred bool try_end(Error *err) { - --trylevel; + // Note: all globals manipulated here should be saved/restored in + // try_enter/try_leave. + trylevel--; // Without this it stops processing all subsequent VimL commands and // generates strange error messages if I e.g. try calling Test() in a @@ -53,12 +126,12 @@ bool try_end(Error *err) did_emsg = false; if (got_int) { - if (did_throw) { + if (current_exception) { // If we got an interrupt, discard the current exception discard_current_exception(); } - api_set_error(err, Exception, _("Keyboard interrupt")); + api_set_error(err, kErrorTypeException, "Keyboard interrupt"); got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; @@ -66,19 +139,18 @@ bool try_end(Error *err) ET_ERROR, NULL, &should_free); - xstrlcpy(err->msg, msg, sizeof(err->msg)); - err->set = true; + api_set_error(err, kErrorTypeException, "%s", msg); free_global_msglist(); if (should_free) { xfree(msg); } - } else if (did_throw) { - api_set_error(err, Exception, "%s", current_exception->value); + } else if (current_exception) { + api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } - return err->set; + return ERROR_SET(err); } /// Recursively expands a vimscript value in a dict @@ -91,8 +163,8 @@ Object dict_get_value(dict_T *dict, String key, Error *err) dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); if (di == NULL) { - api_set_error(err, Validation, _("Key not found")); - return (Object) OBJECT_INIT; + api_set_error(err, kErrorTypeValidation, "Key '%s' not found", key.data); + return (Object)OBJECT_INIT; } return vim_to_object(&di->di_tv); @@ -115,17 +187,18 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, Object rv = OBJECT_INIT; if (dict->dv_lock) { - api_set_error(err, Exception, _("Dictionary is locked")); + api_set_error(err, kErrorTypeException, "Dictionary is locked"); return rv; } if (key.size == 0) { - api_set_error(err, Validation, _("Empty variable names aren't allowed")); + api_set_error(err, kErrorTypeValidation, + "Empty variable names aren't allowed"); return rv; } if (key.size > INT_MAX) { - api_set_error(err, Validation, _("Key length is too high")); + api_set_error(err, kErrorTypeValidation, "Key length is too high"); return rv; } @@ -133,13 +206,13 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, if (di != NULL) { if (di->di_flags & DI_FLAGS_RO) { - api_set_error(err, Exception, _("Key is read-only: %s"), key.data); + api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data); return rv; } else if (di->di_flags & DI_FLAGS_FIX) { - api_set_error(err, Exception, _("Key is fixed: %s"), key.data); + api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data); return rv; } else if (di->di_flags & DI_FLAGS_LOCK) { - api_set_error(err, Exception, _("Key is locked: %s"), key.data); + api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data); return rv; } } @@ -148,7 +221,8 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, // Delete the key if (di == NULL) { // Doesn't exist, fail - api_set_error(err, Validation, _("Key \"%s\" doesn't exist"), key.data); + api_set_error(err, kErrorTypeValidation, "Key does not exist: %s", + key.data); } else { // Return the old value if (retval) { @@ -200,7 +274,7 @@ Object get_option_from(void *from, int type, String name, Error *err) Object rv = OBJECT_INIT; if (name.size == 0) { - api_set_error(err, Validation, _("Empty option name")); + api_set_error(err, kErrorTypeValidation, "Empty option name"); return rv; } @@ -212,8 +286,8 @@ Object get_option_from(void *from, int type, String name, Error *err) if (!flags) { api_set_error(err, - Validation, - _("Invalid option name \"%s\""), + kErrorTypeValidation, + "Invalid option name \"%s\"", name.data); return rv; } @@ -231,14 +305,14 @@ Object get_option_from(void *from, int type, String name, Error *err) rv.data.string.size = strlen(stringval); } else { api_set_error(err, - Exception, - _("Unable to get value for option \"%s\""), + kErrorTypeException, + "Unable to get value for option \"%s\"", name.data); } } else { api_set_error(err, - Exception, - _("Unknown type for option \"%s\""), + kErrorTypeException, + "Unknown type for option \"%s\"", name.data); } @@ -255,7 +329,7 @@ Object get_option_from(void *from, int type, String name, Error *err) void set_option_to(void *to, int type, String name, Object value, Error *err) { if (name.size == 0) { - api_set_error(err, Validation, _("Empty option name")); + api_set_error(err, kErrorTypeValidation, "Empty option name"); return; } @@ -263,8 +337,8 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) if (flags == 0) { api_set_error(err, - Validation, - _("Invalid option name \"%s\""), + kErrorTypeValidation, + "Invalid option name \"%s\"", name.data); return; } @@ -272,15 +346,15 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) if (value.type == kObjectTypeNil) { if (type == SREQ_GLOBAL) { api_set_error(err, - Exception, - _("Unable to unset option \"%s\""), + kErrorTypeException, + "Unable to unset option \"%s\"", name.data); return; } else if (!(flags & SOPT_GLOBAL)) { api_set_error(err, - Exception, - _("Cannot unset option \"%s\" " - "because it doesn't have a global value"), + kErrorTypeException, + "Cannot unset option \"%s\" " + "because it doesn't have a global value", name.data); return; } else { @@ -294,8 +368,8 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) if (flags & SOPT_BOOL) { if (value.type != kObjectTypeBoolean) { api_set_error(err, - Validation, - _("Option \"%s\" requires a boolean value"), + kErrorTypeValidation, + "Option \"%s\" requires a boolean value", name.data); return; } @@ -305,16 +379,16 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) } else if (flags & SOPT_NUM) { if (value.type != kObjectTypeInteger) { api_set_error(err, - Validation, - _("Option \"%s\" requires an integer value"), + kErrorTypeValidation, + "Option \"%s\" requires an integer value", name.data); return; } if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) { api_set_error(err, - Validation, - _("Value for option \"%s\" is outside range"), + kErrorTypeValidation, + "Value for option \"%s\" is outside range", name.data); return; } @@ -324,8 +398,8 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) } else { if (value.type != kObjectTypeString) { api_set_error(err, - Validation, - _("Option \"%s\" requires a string value"), + kErrorTypeValidation, + "Option \"%s\" requires a string value", name.data); return; } @@ -349,7 +423,7 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER #define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ - kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) + kv_push(edata->stack, FLOAT_OBJ((Float)(flt))) #define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ @@ -492,7 +566,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(val) #define TYPVAL_ENCODE_SCOPE static #define TYPVAL_ENCODE_NAME object @@ -558,13 +632,13 @@ buf_T *find_buffer_by_handle(Buffer buffer, Error *err) buf_T *rv = handle_get_buffer(buffer); if (!rv) { - api_set_error(err, Validation, _("Invalid buffer id")); + api_set_error(err, kErrorTypeValidation, "Invalid buffer id"); } return rv; } -win_T * find_window_by_handle(Window window, Error *err) +win_T *find_window_by_handle(Window window, Error *err) { if (window == 0) { return curwin; @@ -573,13 +647,13 @@ win_T * find_window_by_handle(Window window, Error *err) win_T *rv = handle_get_window(window); if (!rv) { - api_set_error(err, Validation, _("Invalid window id")); + api_set_error(err, kErrorTypeValidation, "Invalid window id"); } return rv; } -tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) +tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err) { if (tabpage == 0) { return curtab; @@ -588,12 +662,28 @@ tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) tabpage_T *rv = handle_get_tabpage(tabpage); if (!rv) { - api_set_error(err, Validation, _("Invalid tabpage id")); + api_set_error(err, kErrorTypeValidation, "Invalid tabpage id"); } return rv; } +/// Allocates a String consisting of a single char. Does not support multibyte +/// characters. The resulting string is also NUL-terminated, to facilitate +/// interoperating with code using C strings. +/// +/// @param char the char to convert +/// @return the resulting String, if the input char was NUL, an +/// empty String is returned +String cchar_to_string(char c) +{ + char buf[] = { c, NUL }; + return (String) { + .data = xmemdupz(buf, 1), + .size = (c != NUL) ? 1 : 0 + }; +} + /// Copies a C string into a String (binary safe string, characters + length). /// The resulting string is also NUL-terminated, to facilitate interoperating /// with code using C strings. @@ -614,6 +704,23 @@ String cstr_to_string(const char *str) }; } +/// Copies buffer to an allocated String. +/// The resulting string is also NUL-terminated, to facilitate interoperating +/// with code using C strings. +/// +/// @param buf the buffer to copy +/// @param size length of the buffer +/// @return the resulting String, if the input string was NULL, an +/// empty String is returned +String cbuf_to_string(const char *buf, size_t size) + FUNC_ATTR_NONNULL_ALL +{ + return (String) { + .data = xmemdupz(buf, size), + .size = size + }; +} + /// Creates a String using the given C string. Unlike /// cstr_to_string this function DOES NOT copy the C string. /// @@ -654,12 +761,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeWindow: case kObjectTypeTabpage: case kObjectTypeInteger: - if (obj.data.integer > VARNUMBER_MAX - || obj.data.integer < VARNUMBER_MIN) { - api_set_error(err, Validation, _("Integer value outside range")); - return false; - } - + STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T), + "Integer size must be <= VimL number size"); tv->v_type = VAR_NUMBER; tv->vval.v_number = (varnumber_T)obj.data.integer; break; @@ -680,22 +783,20 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeArray: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; - listitem_T *li = tv_list_item_alloc(); + typval_T li_tv; - if (!object_to_vim(item, &li->li_tv, err)) { - // cleanup - tv_list_item_free(li); + if (!object_to_vim(item, &li_tv, err)) { tv_list_free(list); return false; } - tv_list_append(list, li); + tv_list_append_owned_tv(list, li_tv); } - list->lv_refcount++; + tv_list_ref(list); tv->v_type = VAR_LIST; tv->vval.v_list = list; @@ -710,8 +811,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) String key = item.key; if (key.size == 0) { - api_set_error(err, Validation, - _("Empty dictionary keys aren't allowed")); + api_set_error(err, kErrorTypeValidation, + "Empty dictionary keys aren't allowed"); // cleanup tv_dict_free(dict); return false; @@ -798,6 +899,17 @@ void api_free_dictionary(Dictionary value) xfree(value.items); } +void api_clear_error(Error *value) + FUNC_ATTR_NONNULL_ALL +{ + if (!ERROR_SET(value)) { + return; + } + xfree(value->msg); + value->msg = NULL; + value->type = kErrorTypeNone; +} + Dictionary api_metadata(void) { static Dictionary metadata = ARRAY_DICT_INIT; @@ -805,6 +917,7 @@ Dictionary api_metadata(void) if (!metadata.size) { PUT(metadata, "version", DICTIONARY_OBJ(version_dict())); init_function_metadata(&metadata); + init_ui_event_metadata(&metadata); init_error_type_metadata(&metadata); init_type_metadata(&metadata); } @@ -828,6 +941,28 @@ static void init_function_metadata(Dictionary *metadata) PUT(*metadata, "functions", functions); } +static void init_ui_event_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)ui_events_metadata, + sizeof(ui_events_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object ui_events; + msgpack_rpc_to_object(&unpacked.data, &ui_events); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "ui_events", ui_events); + Array ui_options = ARRAY_DICT_INIT; + ADD(ui_options, STRING_OBJ(cstr_to_string("rgb"))); + for (UIExtension i = 0; i < kUIExtCount; i++) { + ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i]))); + } + PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options)); +} + static void init_error_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; @@ -849,15 +984,18 @@ static void init_type_metadata(Dictionary *metadata) Dictionary types = ARRAY_DICT_INIT; Dictionary buffer_metadata = ARRAY_DICT_INIT; - PUT(buffer_metadata, "id", INTEGER_OBJ(kObjectTypeBuffer)); + PUT(buffer_metadata, "id", + INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT)); PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_"))); Dictionary window_metadata = ARRAY_DICT_INIT; - PUT(window_metadata, "id", INTEGER_OBJ(kObjectTypeWindow)); + PUT(window_metadata, "id", + INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT)); PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_"))); Dictionary tabpage_metadata = ARRAY_DICT_INIT; - PUT(tabpage_metadata, "id", INTEGER_OBJ(kObjectTypeTabpage)); + PUT(tabpage_metadata, "id", + INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT)); PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_"))); PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata)); @@ -867,6 +1005,24 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } +String copy_string(String str) +{ + if (str.data != NULL) { + return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + } else { + return (String)STRING_INIT; + } +} + +Array copy_array(Array array) +{ + Array rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < array.size; i++) { + ADD(rv, copy_object(array.items[i])); + } + return rv; +} + /// Creates a deep clone of an object Object copy_object(Object obj) { @@ -878,15 +1034,10 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(cstr_to_string(obj.data.string.data)); + return STRING_OBJ(copy_string(obj.data.string)); - case kObjectTypeArray: { - Array rv = ARRAY_DICT_INIT; - for (size_t i = 0; i < obj.data.array.size; i++) { - ADD(rv, copy_object(obj.data.array.items[i])); - } - return ARRAY_OBJ(rv); - } + case kObjectTypeArray: + return ARRAY_OBJ(copy_array(obj.data.array)); case kObjectTypeDictionary: { Dictionary rv = ARRAY_DICT_INIT; @@ -911,7 +1062,7 @@ static void set_option_value_for(char *key, { win_T *save_curwin = NULL; tabpage_T *save_curtab = NULL; - bufref_T save_curbuf = { NULL, 0 }; + bufref_T save_curbuf = { NULL, 0, 0 }; try_start(); switch (opt_type) @@ -924,8 +1075,8 @@ static void set_option_value_for(char *key, return; } api_set_error(err, - Exception, - _("Problem while switching windows")); + kErrorTypeException, + "Problem while switching windows"); return; } set_option_value_err(key, numval, stringval, opt_flags, err); @@ -941,7 +1092,7 @@ static void set_option_value_for(char *key, break; } - if (err->set) { + if (ERROR_SET(err)) { return; } @@ -962,6 +1113,64 @@ static void set_option_value_err(char *key, return; } - api_set_error(err, Exception, "%s", errmsg); + api_set_error(err, kErrorTypeException, "%s", errmsg); + } +} + +void api_set_error(Error *err, ErrorType errType, const char *format, ...) + FUNC_ATTR_NONNULL_ALL +{ + assert(kErrorTypeNone != errType); + va_list args1; + va_list args2; + va_start(args1, format); + va_copy(args2, args1); + int len = vsnprintf(NULL, 0, format, args1); + va_end(args1); + assert(len >= 0); + // Limit error message to 1 MB. + size_t bufsize = MIN((size_t)len + 1, 1024 * 1024); + err->msg = xmalloc(bufsize); + vsnprintf(err->msg, bufsize, format, args2); + va_end(args2); + + err->type = errType; +} + +/// Get an array containing dictionaries describing mappings +/// based on mode and buffer id +/// +/// @param mode The abbreviation for the mode +/// @param buf The buffer to get the mapping array. NULL for global +/// @returns An array of maparg() like dictionaries describing mappings +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) +{ + Array mappings = ARRAY_DICT_INIT; + dict_T *const dict = tv_dict_alloc(); + + // Convert the string mode to the integer mode + // that is stored within each mapblock + char_u *p = (char_u *)mode.data; + int int_mode = get_map_mode(&p, 0); + + // Determine the desired buffer value + long buffer_value = (buf == NULL) ? 0 : buf->handle; + + for (int i = 0; i < MAX_MAPHASH; i++) { + for (const mapblock_T *current_maphash = get_maphash(i, buf); + current_maphash; + current_maphash = current_maphash->m_next) { + // Check for correct mode + if (int_mode & current_maphash->m_mode) { + mapblock_fill_dict(dict, current_maphash, buffer_value, false); + ADD(mappings, vim_to_object( + (typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } })); + + tv_dict_clear(dict); + } + } } + tv_dict_free(dict); + + return mappings; } diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 9fe8c351cf..0634764f13 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -6,17 +6,9 @@ #include "nvim/api/private/defs.h" #include "nvim/vim.h" #include "nvim/memory.h" +#include "nvim/ex_eval.h" #include "nvim/lib/kvec.h" -#define api_set_error(err, errtype, ...) \ - do { \ - snprintf((err)->msg, \ - sizeof((err)->msg), \ - __VA_ARGS__); \ - (err)->set = true; \ - (err)->type = kErrorType##errtype; \ - } while (0) - #define OBJECT_OBJ(o) o #define BOOLEAN_OBJ(b) ((Object) { \ @@ -27,7 +19,7 @@ .type = kObjectTypeInteger, \ .data.integer = i }) -#define FLOATING_OBJ(f) ((Object) { \ +#define FLOAT_OBJ(f) ((Object) { \ .type = kObjectTypeFloat, \ .data.floating = f }) @@ -91,6 +83,20 @@ #define api_free_window(value) #define api_free_tabpage(value) +/// Structure used for saving state for :try +/// +/// Used when caller is supposed to be operating when other VimL code is being +/// processed and that “other VimL code” must not be affected. +typedef struct { + except_T *current_exception; + struct msglist *private_msg_list; + const struct msglist *const *msg_list; + int trylevel; + int got_int; + int need_rethrow; + int did_emsg; +} TryState; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" #endif diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 0f0c33f621..b6830d9fcf 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -1,3 +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 <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -15,7 +18,7 @@ /// @param[out] err Error details, if any /// @return List of windows in `tabpage` ArrayOf(Window) nvim_tabpage_list_wins(Tabpage tabpage, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -45,7 +48,7 @@ ArrayOf(Window) nvim_tabpage_list_wins(Tabpage tabpage, Error *err) /// @param[out] err Error details, if any /// @return Variable value Object nvim_tabpage_get_var(Tabpage tabpage, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -66,7 +69,7 @@ void nvim_tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -83,7 +86,7 @@ void nvim_tabpage_set_var(Tabpage tabpage, /// @param name Variable name /// @param[out] err Error details, if any void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -142,7 +145,7 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err) /// @param[out] err Error details, if any /// @return Window handle Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Window rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -170,7 +173,7 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) /// @param[out] err Error details, if any /// @return Tabpage number Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Integer rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -187,9 +190,11 @@ Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) /// @param tabpage Tabpage handle /// @return true if the tabpage is valid, false otherwise Boolean nvim_tabpage_is_valid(Tabpage tabpage) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Error stub = ERROR_INIT; - return find_tab_by_handle(tabpage, &stub) != NULL; + Boolean ret = find_tab_by_handle(tabpage, &stub) != NULL; + api_clear_error(&stub); + return ret; } diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index de60339e5f..4cd2657561 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -1,3 +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 <assert.h> #include <stddef.h> #include <stdint.h> @@ -16,6 +19,7 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" +# include "ui_events_remote.generated.h" #endif typedef struct { @@ -26,51 +30,51 @@ typedef struct { static PMap(uint64_t) *connected_uis = NULL; void remote_ui_init(void) - FUNC_API_NOEXPORT + FUNC_API_NOEXPORT { connected_uis = pmap_new(uint64_t)(); } void remote_ui_disconnect(uint64_t channel_id) - FUNC_API_NOEXPORT + FUNC_API_NOEXPORT { UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); if (!ui) { return; } UIData *data = ui->data; - // destroy pending screen updates - api_free_array(data->buffer); + api_free_array(data->buffer); // Destroy pending screen updates. pmap_del(uint64_t)(connected_uis, channel_id); xfree(ui->data); + ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui); xfree(ui); } void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictionary options, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, Exception, _("UI already attached for channel")); + api_set_error(err, kErrorTypeException, + "UI already attached to channel: %" PRId64, channel_id); return; } if (width <= 0 || height <= 0) { - api_set_error(err, Validation, - _("Expected width > 0 and height > 0")); + api_set_error(err, kErrorTypeValidation, + "Expected width > 0 and height > 0"); return; } UI *ui = xcalloc(1, sizeof(UI)); ui->width = (int)width; ui->height = (int)height; ui->rgb = true; - ui->pum_external = false; ui->resize = remote_ui_resize; ui->clear = remote_ui_clear; ui->eol_clear = remote_ui_eol_clear; ui->cursor_goto = remote_ui_cursor_goto; - ui->cursor_style_set = remote_ui_cursor_style_set; + 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; @@ -83,6 +87,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->put = remote_ui_put; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; + ui->default_colors_set = remote_ui_default_colors_set; ui->update_fg = remote_ui_update_fg; ui->update_bg = remote_ui_update_bg; ui->update_sp = remote_ui_update_sp; @@ -90,11 +95,14 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, 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->event = remote_ui_event; + memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + for (size_t i = 0; i < options.size; i++) { ui_set_option(ui, options.items[i].key, options.items[i].value, err); - if (err->set) { + if (ERROR_SET(err)) { xfree(ui); return; } @@ -120,10 +128,11 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, } void nvim_ui_detach(uint64_t channel_id, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, Exception, _("UI is not attached for channel")); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } remote_ui_disconnect(channel_id); @@ -132,16 +141,17 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, Exception, _("UI is not attached for channel")); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } if (width <= 0 || height <= 0) { - api_set_error(err, Validation, - _("Expected width > 0 and height > 0")); + api_set_error(err, kErrorTypeValidation, + "Expected width > 0 and height > 0"); return; } @@ -153,39 +163,62 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *error) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, Exception, _("UI is not attached for channel")); + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); ui_set_option(ui, name, value, error); - if (!error->set) { + if (!ERROR_SET(error)) { ui_refresh(); } } -static void ui_set_option(UI *ui, String name, Object value, Error *error) { - if (strcmp(name.data, "rgb") == 0) { +static void ui_set_option(UI *ui, String name, Object value, Error *error) +{ + if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { - api_set_error(error, Validation, _("rgb must be a Boolean")); + api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); return; } ui->rgb = value.data.boolean; - } else if (strcmp(name.data, "popupmenu_external") == 0) { + return; + } + + for (UIExtension i = 0; i < kUIExtCount; i++) { + if (strequal(name.data, ui_ext_names[i])) { + if (value.type != kObjectTypeBoolean) { + snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean", + ui_ext_names[i]); + api_set_error(error, kErrorTypeValidation, (char *)IObuff); + return; + } + ui->ui_ext[i] = value.data.boolean; + return; + } + } + + if (strequal(name.data, "popupmenu_external")) { + // LEGACY: Deprecated option, use `ext_cmdline` instead. if (value.type != kObjectTypeBoolean) { - api_set_error(error, Validation, - _("popupmenu_external must be a Boolean")); + api_set_error(error, kErrorTypeValidation, + "popupmenu_external must be a Boolean"); return; } - ui->pum_external = value.data.boolean; - } else { - api_set_error(error, Validation, _("No such ui option")); + ui->ui_ext[kUIPopupmenu] = value.data.boolean; + return; } + + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); +#undef UI_EXT_OPTION } +/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). static void push_call(UI *ui, char *name, Array args) { Array call = ARRAY_DICT_INIT; @@ -208,213 +241,23 @@ static void push_call(UI *ui, char *name, Array args) kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call; } -static void remote_ui_resize(UI *ui, int width, int height) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(width)); - ADD(args, INTEGER_OBJ(height)); - push_call(ui, "resize", args); -} - -static void remote_ui_clear(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "clear", args); -} - -static void remote_ui_eol_clear(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "eol_clear", args); -} - -static void remote_ui_cursor_goto(UI *ui, int row, int col) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(row)); - ADD(args, INTEGER_OBJ(col)); - push_call(ui, "cursor_goto", args); -} - -static void remote_ui_update_menu(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "update_menu", args); -} - -static void remote_ui_busy_start(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "busy_start", args); -} - -static void remote_ui_busy_stop(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "busy_stop", args); -} - -static void remote_ui_mouse_on(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "mouse_on", args); -} - -static void remote_ui_mouse_off(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "mouse_off", args); -} - -static void remote_ui_mode_change(UI *ui, int mode) -{ - Array args = ARRAY_DICT_INIT; - if (mode == INSERT) { - ADD(args, STRING_OBJ(cstr_to_string("insert"))); - } else if (mode == REPLACE) { - ADD(args, STRING_OBJ(cstr_to_string("replace"))); - } else if (mode == CMDLINE) { - ADD(args, STRING_OBJ(cstr_to_string("cmdline"))); - } else { - assert(mode == NORMAL); - ADD(args, STRING_OBJ(cstr_to_string("normal"))); - } - push_call(ui, "mode_change", args); -} - -static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left, - int right) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(top)); - ADD(args, INTEGER_OBJ(bot)); - ADD(args, INTEGER_OBJ(left)); - ADD(args, INTEGER_OBJ(right)); - push_call(ui, "set_scroll_region", args); -} - -static void remote_ui_scroll(UI *ui, int count) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(count)); - push_call(ui, "scroll", args); -} - -static void remote_ui_cursor_style_set(UI *ui, bool enabled, Dictionary data) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, BOOLEAN_OBJ(enabled)); - ADD(args, copy_object(DICTIONARY_OBJ(data))); - push_call(ui, "cursor_style_set", args); -} static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) { Array args = ARRAY_DICT_INIT; - Dictionary hl = ARRAY_DICT_INIT; - - if (attrs.bold) { - PUT(hl, "bold", BOOLEAN_OBJ(true)); - } - - if (attrs.underline) { - PUT(hl, "underline", BOOLEAN_OBJ(true)); - } - - if (attrs.undercurl) { - PUT(hl, "undercurl", BOOLEAN_OBJ(true)); - } - - if (attrs.italic) { - PUT(hl, "italic", BOOLEAN_OBJ(true)); - } - - if (attrs.reverse) { - PUT(hl, "reverse", BOOLEAN_OBJ(true)); - } - - if (attrs.foreground != -1) { - PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); - } - - if (attrs.background != -1) { - PUT(hl, "background", INTEGER_OBJ(attrs.background)); - } - - if (attrs.special != -1) { - PUT(hl, "special", INTEGER_OBJ(attrs.special)); - } + Dictionary hl = hlattrs2dict(&attrs, ui->rgb); ADD(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); } -static void remote_ui_put(UI *ui, uint8_t *data, size_t size) -{ - Array args = ARRAY_DICT_INIT; - String str = { .data = xmemdupz(data, size), .size = size }; - ADD(args, STRING_OBJ(str)); - push_call(ui, "put", args); -} - -static void remote_ui_bell(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "bell", args); -} - -static void remote_ui_visual_bell(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "visual_bell", args); -} - -static void remote_ui_update_fg(UI *ui, int fg) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(fg)); - push_call(ui, "update_fg", args); -} - -static void remote_ui_update_bg(UI *ui, int bg) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(bg)); - push_call(ui, "update_bg", args); -} - -static void remote_ui_update_sp(UI *ui, int sp) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(sp)); - push_call(ui, "update_sp", args); -} - static void remote_ui_flush(UI *ui) { UIData *data = ui->data; - channel_send_event(data->channel_id, "redraw", data->buffer); - data->buffer = (Array)ARRAY_DICT_INIT; -} - -static void remote_ui_suspend(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "suspend", args); -} - -static void remote_ui_set_title(UI *ui, char *title) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(title))); - push_call(ui, "set_title", args); -} - -static void remote_ui_set_icon(UI *ui, char *icon) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(icon))); - push_call(ui, "set_icon", args); + if (data->buffer.size > 0) { + rpc_send_event(data->channel_id, "redraw", data->buffer); + data->buffer = (Array)ARRAY_DICT_INIT; + } } static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h new file mode 100644 index 0000000000..96d494460b --- /dev/null +++ b/src/nvim/api/ui_events.in.h @@ -0,0 +1,99 @@ +#ifndef NVIM_API_UI_EVENTS_IN_H +#define NVIM_API_UI_EVENTS_IN_H + +// This file is not compiled, just parsed for definitons +#ifdef INCLUDE_GENERATED_DECLARATIONS +# error "don't include this file, include nvim/ui.h" +#endif + +#include "nvim/api/private/defs.h" +#include "nvim/func_attr.h" +#include "nvim/ui.h" + +void resize(Integer width, Integer height) + FUNC_API_SINCE(3); +void clear(void) + FUNC_API_SINCE(3); +void eol_clear(void) + FUNC_API_SINCE(3); +void cursor_goto(Integer row, Integer col) + FUNC_API_SINCE(3); +void mode_info_set(Boolean enabled, Array cursor_styles) + FUNC_API_SINCE(3); +void update_menu(void) + FUNC_API_SINCE(3); +void busy_start(void) + FUNC_API_SINCE(3); +void busy_stop(void) + FUNC_API_SINCE(3); +void mouse_on(void) + FUNC_API_SINCE(3); +void mouse_off(void) + FUNC_API_SINCE(3); +void mode_change(String mode, Integer mode_idx) + FUNC_API_SINCE(3); +void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) + FUNC_API_SINCE(3); +void scroll(Integer count) + FUNC_API_SINCE(3); +void highlight_set(HlAttrs attrs) + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void put(String str) + FUNC_API_SINCE(3); +void bell(void) + FUNC_API_SINCE(3); +void visual_bell(void) + FUNC_API_SINCE(3); +void flush(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; +void update_fg(Integer fg) + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void update_bg(Integer bg) + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void update_sp(Integer sp) + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) + FUNC_API_SINCE(4); +void suspend(void) + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void set_title(String title) + FUNC_API_SINCE(3); +void set_icon(String icon) + FUNC_API_SINCE(3); +void option_set(String name, Object value) + FUNC_API_SINCE(4); + +void popupmenu_show(Array items, Integer selected, Integer row, Integer col) + 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) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +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; +void cmdline_special_char(String c, Boolean shift, Integer level) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void cmdline_hide(Integer level) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void cmdline_block_show(Array lines) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void cmdline_block_append(Array lines) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +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_ONLY; +void wildmenu_select(Integer selected) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void wildmenu_hide(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +#endif // NVIM_API_UI_EVENTS_IN_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 6926436d2f..af3d379870 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1,3 +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 <assert.h> #include <stdint.h> #include <inttypes.h> @@ -10,8 +13,10 @@ #include "nvim/ascii.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/buffer.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/file_search.h" @@ -24,9 +29,14 @@ #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/option.h" +#include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/os/process.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/ui.h" #define LINE_BUFFER_SIZE 4096 @@ -35,20 +45,63 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); try_end(err); } +/// Gets a highlight definition by name. +/// +/// @param name Highlight group name +/// @param rgb Export RGB colors +/// @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) + FUNC_API_SINCE(3) +{ + Dictionary result = ARRAY_DICT_INIT; + int id = syn_name2id((const char_u *)name.data); + + if (id == 0) { + api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", + name.data); + return result; + } + result = nvim_get_hl_by_id(id, rgb, err); + return result; +} + +/// Gets a highlight definition by id. |hlID()| +/// +/// @param hl_id Highlight id as returned by |hlID()| +/// @param rgb Export RGB colors +/// @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) + 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); + return dic; + } + int attrcode = syn_id2attr((int)hl_id); + return hl_get_attr_by_id(attrcode, rgb, err); +} + /// Passes input keys to Nvim. /// On VimL error: Does not fail, but updates v:errmsg. /// @@ -58,7 +111,7 @@ void nvim_command(String command, Error *err) /// @see feedkeys() /// @see vim_strsave_escape_csi void nvim_feedkeys(String keys, String mode, Boolean escape_csi) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { bool remap = true; bool insert = false; @@ -119,54 +172,89 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) /// On VimL error: Does not fail, but updates v:errmsg. /// /// Unlike `nvim_feedkeys`, this uses a lower-level input buffer and the call -/// is not deferred. This is the most reliable way to emulate real user input. +/// is not deferred. This is the most reliable way to send real user input. +/// +/// @note |keycodes| like <CR> are translated, so "<" is special. +/// To input a literal "<", send <LT>. /// /// @param keys to be typed /// @return Number of bytes actually written (can be fewer than /// requested if the buffer becomes full). Integer nvim_input(String keys) - FUNC_API_SINCE(1) FUNC_API_ASYNC + FUNC_API_SINCE(1) FUNC_API_ASYNC { return (Integer)input_enqueue(keys); } -/// Replaces any terminal codes with the internal representation +/// Replaces terminal codes and |keycodes| (<CR>, <Esc>, ...) in a string with +/// the internal representation. /// +/// @param str String to be converted. +/// @param from_part Legacy Vim parameter. Usually true. +/// @param do_lt Also translate <lt>. Ignored if `special` is false. +/// @param special Replace |keycodes|, e.g. <CR> becomes a "\n" char. /// @see replace_termcodes /// @see cpoptions String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { if (str.size == 0) { // Empty string - return str; + return (String) { .data = NULL, .size = 0 }; } char *ptr = NULL; - // Set 'cpoptions' the way we want it. - // FLAG_CPO_BSLASH set - backslashes are *not* treated specially - // FLAG_CPO_KEYCODE set - keycodes are *not* reverse-engineered - // FLAG_CPO_SPECI unset - <Key> sequences *are* interpreted - // The third from end parameter of replace_termcodes() is true so that the - // <lt> sequence is recognised - needed for a real backslash. replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr, from_part, do_lt, special, CPO_TO_CPO_FLAGS); return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) - FUNC_API_SINCE(1) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) + FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); - if (err->set) { - return (String) STRING_INIT; + if (ERROR_SET(err)) { + goto theend; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); + if (capture_local.ga_len > 1) { + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. + } + +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). @@ -177,7 +265,7 @@ String nvim_command_output(String str, Error *err) /// @param[out] err Error details, if any /// @return Evaluation result or expanded object Object nvim_eval(String expr, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Object rv = OBJECT_INIT; // Evaluate the expression @@ -185,7 +273,7 @@ Object nvim_eval(String expr, Error *err) typval_T rettv; if (eval0((char_u *)expr.data, &rettv, NULL, true) == FAIL) { - api_set_error(err, Exception, "Failed to evaluate expression"); + api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); } if (!try_end(err)) { @@ -199,7 +287,8 @@ Object nvim_eval(String expr, Error *err) return rv; } -/// Calls a VimL function with the given arguments. +/// Calls a VimL function with the given arguments +/// /// On VimL error: Returns a generic error; v:errmsg is not updated. /// /// @param fname Function to call @@ -207,12 +296,12 @@ Object nvim_eval(String expr, Error *err) /// @param[out] err Error details, if any /// @return Result of the function call Object nvim_call_function(String fname, Array args, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { - api_set_error(err, Validation, - _("Function called with too many arguments.")); + api_set_error(err, kErrorTypeValidation, + "Function called with too many arguments."); return rv; } @@ -234,7 +323,7 @@ Object nvim_call_function(String fname, Array args, Error *err) curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, true, NULL, NULL); if (r == FAIL) { - api_set_error(err, Exception, _("Error calling function.")); + api_set_error(err, kErrorTypeException, "Error calling function."); } if (!try_end(err)) { rv = vim_to_object(&rettv); @@ -249,31 +338,49 @@ free_vim_args: return rv; } +/// Execute lua code. Parameters (if any) are available as `...` inside the +/// chunk. The chunk can return a value. +/// +/// Only statements are executed. To evaluate an expression, prefix it +/// with `return`: return my_function(...) +/// +/// @param code lua code to execute +/// @param args Arguments to the code +/// @param[out] err Details of an error encountered while parsing +/// or executing the lua code. +/// +/// @return Return value of lua code if present or NIL. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + /// Calculates the number of display cells occupied by `text`. /// <Tab> counts as one cell. /// /// @param text Some text /// @param[out] err Error details, if any /// @return Number of cells -Integer nvim_strwidth(String str, Error *err) - FUNC_API_SINCE(1) +Integer nvim_strwidth(String text, Error *err) + FUNC_API_SINCE(1) { - if (str.size > INT_MAX) { - api_set_error(err, Validation, _("String length is too high")); + if (text.size > INT_MAX) { + api_set_error(err, kErrorTypeValidation, "String length is too high"); return 0; } - return (Integer) mb_string2cells((char_u *) str.data); + return (Integer)mb_string2cells((char_u *)text.data); } /// Gets the paths contained in 'runtimepath'. /// /// @return List of paths ArrayOf(String) nvim_list_runtime_paths(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; - uint8_t *rtp = p_rtp; + char_u *rtp = p_rtp; if (*rtp == NUL) { // No paths @@ -287,13 +394,14 @@ ArrayOf(String) nvim_list_runtime_paths(void) } rtp++; } + rv.size++; // Allocate memory for the copies - rv.items = xmalloc(sizeof(Object) * rv.size); + rv.items = xmalloc(sizeof(*rv.items) * rv.size); // Reset the position rtp = p_rtp; // Start copying - for (size_t i = 0; i < rv.size && *rtp != NUL; i++) { + for (size_t i = 0; i < rv.size; i++) { rv.items[i].type = kObjectTypeString; rv.items[i].data.string.data = xmalloc(MAXPATHL); // Copy the path from 'runtimepath' to rv.items[i] @@ -312,10 +420,10 @@ ArrayOf(String) nvim_list_runtime_paths(void) /// @param dir Directory path /// @param[out] err Error details, if any void nvim_set_current_dir(String dir, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { if (dir.size >= MAXPATHL) { - api_set_error(err, Validation, _("Directory string is too long")); + api_set_error(err, kErrorTypeValidation, "Directory string is too long"); return; } @@ -327,7 +435,7 @@ void nvim_set_current_dir(String dir, Error *err) if (vim_chdir((char_u *)string, kCdScopeGlobal)) { if (!try_end(err)) { - api_set_error(err, Exception, _("Failed to change directory")); + api_set_error(err, kErrorTypeException, "Failed to change directory"); } return; } @@ -341,7 +449,7 @@ void nvim_set_current_dir(String dir, Error *err) /// @param[out] err Error details, if any /// @return Current line string String nvim_get_current_line(Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } @@ -351,7 +459,7 @@ String nvim_get_current_line(Error *err) /// @param line Line contents /// @param[out] err Error details, if any void nvim_set_current_line(String line, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); } @@ -360,7 +468,7 @@ void nvim_set_current_line(String line, Error *err) /// /// @param[out] err Error details, if any void nvim_del_current_line(Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } @@ -371,7 +479,7 @@ void nvim_del_current_line(Error *err) /// @param[out] err Error details, if any /// @return Variable value Object nvim_get_var(String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return dict_get_value(&globvardict, name, err); } @@ -382,7 +490,7 @@ Object nvim_get_var(String name, Error *err) /// @param value Variable value /// @param[out] err Error details, if any void nvim_set_var(String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { dict_set_var(&globvardict, name, value, false, false, err); } @@ -392,34 +500,23 @@ void nvim_set_var(String name, Object value, Error *err) /// @param name Variable name /// @param[out] err Error details, if any void nvim_del_var(String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { dict_set_var(&globvardict, name, NIL, true, false, err); } -/// Sets a global variable -/// /// @deprecated -/// -/// @param name Variable name -/// @param value Variable value -/// @param[out] err Error details, if any +/// @see nvim_set_var /// @return Old value or nil if there was no previous value. -/// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @warning May return nil if there was no previous value +/// OR if previous value was `v:null`. Object vim_set_var(String name, Object value, Error *err) { return dict_set_var(&globvardict, name, value, false, true, err); } -/// Removes a global variable -/// /// @deprecated -/// -/// @param name Variable name -/// @param[out] err Error details, if any -/// @return Old value +/// @see nvim_del_var Object vim_del_var(String name, Error *err) { return dict_set_var(&globvardict, name, NIL, true, true, err); @@ -431,7 +528,7 @@ Object vim_del_var(String name, Error *err) /// @param[out] err Error details, if any /// @return Variable value Object nvim_get_vvar(String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return dict_get_value(&vimvardict, name, err); } @@ -442,7 +539,7 @@ Object nvim_get_vvar(String name, Error *err) /// @param[out] err Error details, if any /// @return Option value (global) Object nvim_get_option(String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return get_option_from(NULL, SREQ_GLOBAL, name, err); } @@ -453,36 +550,38 @@ Object nvim_get_option(String name, Error *err) /// @param value New option value /// @param[out] err Error details, if any void nvim_set_option(String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { set_option_to(NULL, SREQ_GLOBAL, name, value, err); } -/// Writes a message to vim output buffer +/// Writes a message to the Vim output buffer. Does not append "\n", the +/// message is buffered (won't display) until a linefeed is written. /// /// @param str Message void nvim_out_write(String str) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { write_msg(str, false); } -/// Writes a message to vim error buffer +/// Writes a message to the Vim error buffer. Does not append "\n", the +/// message is buffered (won't display) until a linefeed is written. /// /// @param str Message void nvim_err_write(String str) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { write_msg(str, true); } -/// Writes a message to vim error buffer. Appends a linefeed to ensure all -/// contents are written. +/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is +/// flushed (and displayed). /// /// @param str Message /// @see nvim_err_write() void nvim_err_writeln(String str) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { nvim_err_write(str); nvim_err_write((String) { .data = "\n", .size = 1 }); @@ -492,7 +591,7 @@ void nvim_err_writeln(String str) /// /// @return List of buffer handles ArrayOf(Buffer) nvim_list_bufs(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; @@ -514,17 +613,17 @@ ArrayOf(Buffer) nvim_list_bufs(void) /// /// @return Buffer handle Buffer nvim_get_current_buf(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return curbuf->handle; } /// Sets the current buffer /// -/// @param id Buffer handle +/// @param buffer Buffer handle /// @param[out] err Error details, if any void nvim_set_current_buf(Buffer buffer, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -536,8 +635,8 @@ void nvim_set_current_buf(Buffer buffer, Error *err) int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); if (!try_end(err) && result == FAIL) { api_set_error(err, - Exception, - _("Failed to switch to buffer %d"), + kErrorTypeException, + "Failed to switch to buffer %d", buffer); } } @@ -546,7 +645,7 @@ void nvim_set_current_buf(Buffer buffer, Error *err) /// /// @return List of window handles ArrayOf(Window) nvim_list_wins(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; @@ -568,16 +667,16 @@ ArrayOf(Window) nvim_list_wins(void) /// /// @return Window handle Window nvim_get_current_win(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return curwin->handle; } /// Sets the current window /// -/// @param handle Window handle +/// @param window Window handle void nvim_set_current_win(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -589,8 +688,8 @@ void nvim_set_current_win(Window window, Error *err) goto_tabpage_win(win_find_tabpage(win), win); if (!try_end(err) && win != curwin) { api_set_error(err, - Exception, - _("Failed to switch to window %d"), + kErrorTypeException, + "Failed to switch to window %d", window); } } @@ -599,7 +698,7 @@ void nvim_set_current_win(Window window, Error *err) /// /// @return List of tabpage handles ArrayOf(Tabpage) nvim_list_tabpages(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; @@ -621,17 +720,17 @@ ArrayOf(Tabpage) nvim_list_tabpages(void) /// /// @return Tabpage handle Tabpage nvim_get_current_tabpage(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { return curtab->handle; } /// Sets the current tabpage /// -/// @param handle Tabpage handle +/// @param tabpage Tabpage handle /// @param[out] err Error details, if any void nvim_set_current_tabpage(Tabpage tabpage, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { tabpage_T *tp = find_tab_by_handle(tabpage, err); @@ -643,8 +742,8 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) goto_tabpage_tp(tp, true, true); if (!try_end(err) && tp != curtab) { api_set_error(err, - Exception, - _("Failed to switch to tabpage %d"), + kErrorTypeException, + "Failed to switch to tabpage %d", tabpage); } } @@ -654,13 +753,13 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string void nvim_subscribe(uint64_t channel_id, String event) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN); char e[METHOD_MAXLEN + 1]; memcpy(e, event.data, length); e[length] = NUL; - channel_subscribe(channel_id, e); + rpc_subscribe(channel_id, e); } /// Unsubscribes to event broadcasts @@ -668,7 +767,7 @@ void nvim_subscribe(uint64_t channel_id, String event) /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string void nvim_unsubscribe(uint64_t channel_id, String event) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { size_t length = (event.size < METHOD_MAXLEN ? event.size : @@ -676,17 +775,17 @@ void nvim_unsubscribe(uint64_t channel_id, String event) char e[METHOD_MAXLEN + 1]; memcpy(e, event.data, length); e[length] = NUL; - channel_unsubscribe(channel_id, e); + rpc_unsubscribe(channel_id, e); } Integer nvim_get_color_by_name(String name) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { - return name_to_color((uint8_t *)name.data); + return name_to_color((char_u *)name.data); } Dictionary nvim_get_color_map(void) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Dictionary colors = ARRAY_DICT_INIT; @@ -698,8 +797,40 @@ Dictionary nvim_get_color_map(void) } +/// Gets the current mode. |mode()| +/// "blocking" is true if Nvim is waiting for input. +/// +/// @returns Dictionary { "mode": String, "blocking": Boolean } +Dictionary nvim_get_mode(void) + FUNC_API_SINCE(2) FUNC_API_ASYNC +{ + Dictionary rv = ARRAY_DICT_INIT; + char *modestr = get_mode(); + bool blocked = input_blocking(); + + PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr))); + PUT(rv, "blocking", BOOLEAN_OBJ(blocked)); + + return rv; +} + +/// Gets a list of dictionaries describing global (non-buffer) mappings. +/// The "buffer" key in the returned dictionary is always zero. +/// +/// @param mode Mode short-name ("n", "i", "v", ...) +/// @returns Array of maparg()-like dictionaries describing mappings +ArrayOf(Dictionary) nvim_get_keymap(String mode) + FUNC_API_SINCE(3) +{ + return keymap_array(mode, NULL); +} + +/// Returns a 2-tuple (Array), where item 0 is the current channel id and item +/// 1 is the |api-metadata| map (Dictionary). +/// +/// @returns 2-tuple [{channel-id}, {api-metadata}] Array nvim_get_api_info(uint64_t channel_id) - FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { Array rv = ARRAY_DICT_INIT; @@ -732,7 +863,7 @@ Array nvim_get_api_info(uint64_t channel_id) /// which resulted in an error, the error type and the error message. If an /// error ocurred, the values from all preceding calls will still be returned. Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { Array rv = ARRAY_DICT_INIT; Array results = ARRAY_DICT_INIT; @@ -742,30 +873,30 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) for (i = 0; i < calls.size; i++) { if (calls.items[i].type != kObjectTypeArray) { api_set_error(err, - Validation, - _("All items in calls array must be arrays")); + kErrorTypeValidation, + "All items in calls array must be arrays"); goto validation_error; } Array call = calls.items[i].data.array; if (call.size != 2) { api_set_error(err, - Validation, - _("All items in calls array must be arrays of size 2")); + kErrorTypeValidation, + "All items in calls array must be arrays of size 2"); goto validation_error; } if (call.items[0].type != kObjectTypeString) { api_set_error(err, - Validation, - _("name must be String")); + kErrorTypeValidation, + "Name must be String"); goto validation_error; } String name = call.items[0].data.string; if (call.items[1].type != kObjectTypeArray) { api_set_error(err, - Validation, - _("args must be Array")); + kErrorTypeValidation, + "Args must be Array"); goto validation_error; } Array args = call.items[1].data.array; @@ -773,7 +904,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) MsgpackRpcRequestHandler handler = msgpack_rpc_get_handler_for(name.data, name.size); Object result = handler.fn(channel_id, args, &nested_error); - if (nested_error.set) { + if (ERROR_SET(&nested_error)) { // error handled after loop break; } @@ -782,7 +913,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) } ADD(rv, ARRAY_OBJ(results)); - if (nested_error.set) { + if (ERROR_SET(&nested_error)) { Array errval = ARRAY_DICT_INIT; ADD(errval, INTEGER_OBJ((Integer)i)); ADD(errval, INTEGER_OBJ(nested_error.type)); @@ -791,13 +922,469 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) } else { ADD(rv, NIL); } - return rv; + goto theend; validation_error: api_free_array(results); +theend: + api_clear_error(&nested_error); return rv; } +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +/// @cond DOXYGEN_NOT_A_FUNCTION +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +/// @endcond + +/// Parse a VimL expression +/// +/// @param[in] expr Expression to parse. Is always treated as a single line. +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "<C-r>=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). +/// +/// @return AST: top-level dictionary with these keys: +/// +/// "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// +/// "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// "arg": String, error message argument. +/// +/// "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// +/// @note: “Sucessfully parsed” here means “participated in AST +/// creation”, not “till the first error”. +/// +/// "ast": AST, either nil or a dictionary with these keys: +/// +/// "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// "start": a pair [line, column] describing where node is “started” +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// "len": “length” of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. +/// +/// Local values (present only for certain nodes): +/// +/// "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. +/// "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. +/// "ivalue": Integer, integer value for "Integer" nodes. +/// "fvalue": Float, floating-point value for "Float" nodes. +/// "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + Error *err) + FUNC_API_SINCE(4) FUNC_API_ASYNC +{ + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': { pflags |= kExprFlagsMulti; break; } + case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } + case NUL: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + default: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + } + ParserLine plines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t ret_size = ( + 2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); + Dictionary ret = { + .items = xmalloc(ret_size * sizeof(ret.items[0])), + .size = 0, + .capacity = ret_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ast"), + .value = NIL, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? plines[0].size + : pstate.pos.col)), + }; + if (east.err.msg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), + }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + kvi_destroy(colors); + + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + if (cur_item.ret_node_p->type == kObjectTypeNil) { + const size_t ret_node_items_size = (size_t)( + 3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + + 0); + Dictionary ret_node = { + .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), + .capacity = ret_node_items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->next, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), + }; + break; + } + case kExprNodeOption: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + } + case kExprNodePlainIdentifier: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodePlainKey: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodeEnvironment: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + } + case kExprNodeRegister: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + } + case kExprNodeComparison: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string( + ccs_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + } + case kExprNodeFloat: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + } + case kExprNodeInteger: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)( + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: { + break; + } + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); + + assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + return ret; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing @@ -813,7 +1400,7 @@ static void write_msg(String message, bool to_err) #define PUSH_CHAR(i, pos, line_buf, msg) \ if (message.data[i] == NL || pos == LINE_BUFFER_SIZE - 1) { \ line_buf[pos] = NUL; \ - msg((uint8_t *)line_buf); \ + msg((char_u *)line_buf); \ pos = 0; \ continue; \ } \ @@ -831,3 +1418,160 @@ static void write_msg(String message, bool to_err) --no_wait_return; msg_end(); } + +// Functions used for testing purposes + +/// Returns object given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] obj Object to return. +/// +/// @return its argument. +Object nvim__id(Object obj) +{ + return copy_object(obj); +} + +/// Returns array given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] arr Array to return. +/// +/// @return its argument. +Array nvim__id_array(Array arr) +{ + return copy_object(ARRAY_OBJ(arr)).data.array; +} + +/// Returns dictionary given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] dct Dictionary to return. +/// +/// @return its argument. +Dictionary nvim__id_dictionary(Dictionary dct) +{ + return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; +} + +/// Returns floating-point value given as argument +/// +/// This API function is used for testing. One should not rely on its presence +/// in plugins. +/// +/// @param[in] flt Value to return. +/// +/// @return its argument. +Float nvim__id_float(Float flt) +{ + return flt; +} + +/// Gets internal stats. +/// +/// @return Map of various internal stats. +Dictionary nvim__stats(void) +{ + Dictionary rv = ARRAY_DICT_INIT; + PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); + PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + return rv; +} + +/// Gets a list of dictionaries representing attached UIs. +/// +/// @return Array of UI dictionaries +Array nvim_list_uis(void) + FUNC_API_SINCE(4) +{ + return ui_array(); +} + +/// Gets the immediate children of process `pid`. +/// +/// @return Array of child process ids, empty if process not found. +Array nvim_get_proc_children(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Array rvobj = ARRAY_DICT_INIT; + int *proc_list = NULL; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + goto end; + } + + size_t proc_count; + int rv = os_proc_children((int)pid, &proc_list, &proc_count); + if (rv != 0) { + // syscall failed (possibly because of kernel options), try shelling out. + DLOG("fallback to vim._os_proc_children()"); + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray) { + rvobj = o.data.array; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%" PRId64 " error=%d", + pid, rv); + } + goto end; + } + + for (size_t i = 0; i < proc_count; i++) { + ADD(rvobj, INTEGER_OBJ(proc_list[i])); + } + +end: + xfree(proc_list); + return rvobj; +} + +/// Gets info describing process `pid`. +/// +/// @return Map of process properties, or NIL if process not found. +Object nvim_get_proc(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Object rvobj = OBJECT_INIT; + rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; + rvobj.type = kObjectTypeDictionary; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + return NIL; + } +#ifdef WIN32 + rvobj.data.dictionary = os_proc_info((int)pid); + if (rvobj.data.dictionary.size == 0) { // Process not found. + return NIL; + } +#else + // Cross-platform process info APIs are miserable, so use `ps` instead. + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray && o.data.array.size == 0) { + return NIL; // Process not found. + } else if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process info. pid=%" PRId64, pid); + } +#endif + return rvobj; +} diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 3c564ada99..9bc91ef8fb 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -1,3 +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 <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -19,7 +22,7 @@ /// @param[out] err Error details, if any /// @return Buffer handle Buffer nvim_win_get_buf(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -36,7 +39,7 @@ Buffer nvim_win_get_buf(Window window, Error *err) /// @param[out] err Error details, if any /// @return (row, col) tuple ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); @@ -55,19 +58,19 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) /// @param pos (row, col) tuple representing the new position /// @param[out] err Error details, if any void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); - if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger - || pos.items[1].type != kObjectTypeInteger) { - api_set_error(err, - Validation, - _("Argument \"pos\" must be a [row, col] array")); + if (!win) { return; } - if (!win) { + if (pos.size != 2 || pos.items[0].type != kObjectTypeInteger + || pos.items[1].type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "Argument \"pos\" must be a [row, col] array"); return; } @@ -75,12 +78,12 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) int64_t col = pos.items[1].data.integer; if (row <= 0 || row > win->w_buffer->b_ml.ml_line_count) { - api_set_error(err, Validation, _("Cursor position outside buffer")); + api_set_error(err, kErrorTypeValidation, "Cursor position outside buffer"); return; } if (col > MAXCOL || col < 0) { - api_set_error(err, Validation, _("Column value outside range")); + api_set_error(err, kErrorTypeValidation, "Column value outside range"); return; } @@ -102,7 +105,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) /// @param[out] err Error details, if any /// @return Height as a count of rows Integer nvim_win_get_height(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -120,7 +123,7 @@ Integer nvim_win_get_height(Window window, Error *err) /// @param height Height as a count of rows /// @param[out] err Error details, if any void nvim_win_set_height(Window window, Integer height, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -129,7 +132,7 @@ void nvim_win_set_height(Window window, Integer height, Error *err) } if (height > INT_MAX || height < INT_MIN) { - api_set_error(err, Validation, _("Height value outside range")); + api_set_error(err, kErrorTypeValidation, "Height value outside range"); return; } @@ -147,7 +150,7 @@ void nvim_win_set_height(Window window, Integer height, Error *err) /// @param[out] err Error details, if any /// @return Width as a count of columns Integer nvim_win_get_width(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -165,7 +168,7 @@ Integer nvim_win_get_width(Window window, Error *err) /// @param width Width as a count of columns /// @param[out] err Error details, if any void nvim_win_set_width(Window window, Integer width, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -174,7 +177,7 @@ void nvim_win_set_width(Window window, Integer width, Error *err) } if (width > INT_MAX || width < INT_MIN) { - api_set_error(err, Validation, _("Width value outside range")); + api_set_error(err, kErrorTypeValidation, "Width value outside range"); return; } @@ -193,7 +196,7 @@ void nvim_win_set_width(Window window, Integer width, Error *err) /// @param[out] err Error details, if any /// @return Variable value Object nvim_win_get_var(Window window, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -211,7 +214,7 @@ Object nvim_win_get_var(Window window, String name, Error *err) /// @param value Variable value /// @param[out] err Error details, if any void nvim_win_set_var(Window window, String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -228,7 +231,7 @@ void nvim_win_set_var(Window window, String name, Object value, Error *err) /// @param name Variable name /// @param[out] err Error details, if any void nvim_win_del_var(Window window, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -288,7 +291,7 @@ Object window_del_var(Window window, String name, Error *err) /// @param[out] err Error details, if any /// @return Option value Object nvim_win_get_option(Window window, String name, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -307,7 +310,7 @@ Object nvim_win_get_option(Window window, String name, Error *err) /// @param value Option value /// @param[out] err Error details, if any void nvim_win_set_option(Window window, String name, Object value, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -324,7 +327,7 @@ void nvim_win_set_option(Window window, String name, Object value, Error *err) /// @param[out] err Error details, if any /// @return (row, col) tuple with the window position ArrayOf(Integer, 2) nvim_win_get_position(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); @@ -343,7 +346,7 @@ ArrayOf(Integer, 2) nvim_win_get_position(Window window, Error *err) /// @param[out] err Error details, if any /// @return Tabpage that contains the window Tabpage nvim_win_get_tabpage(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Tabpage rv = 0; win_T *win = find_window_by_handle(window, err); @@ -361,7 +364,7 @@ Tabpage nvim_win_get_tabpage(Window window, Error *err) /// @param[out] err Error details, if any /// @return Window number Integer nvim_win_get_number(Window window, Error *err) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { int rv = 0; win_T *win = find_window_by_handle(window, err); @@ -381,9 +384,11 @@ Integer nvim_win_get_number(Window window, Error *err) /// @param window Window handle /// @return true if the window is valid, false otherwise Boolean nvim_win_is_valid(Window window) - FUNC_API_SINCE(1) + FUNC_API_SINCE(1) { Error stub = ERROR_INIT; - return find_window_by_handle(window, &stub) != NULL; + Boolean ret = find_window_by_handle(window, &stub) != NULL; + api_clear_error(&stub); + return ret; } |