diff options
Diffstat (limited to 'src/nvim/api')
| -rw-r--r-- | src/nvim/api/buffer.c | 905 | ||||
| -rw-r--r-- | src/nvim/api/dispatch_deprecated.lua | 69 | ||||
| -rw-r--r-- | src/nvim/api/private/defs.h | 47 | ||||
| -rw-r--r-- | src/nvim/api/private/dispatch.c | 52 | ||||
| -rw-r--r-- | src/nvim/api/private/dispatch.h | 23 | ||||
| -rw-r--r-- | src/nvim/api/private/handle.c | 19 | ||||
| -rw-r--r-- | src/nvim/api/private/handle.h | 6 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.c | 688 | ||||
| -rw-r--r-- | src/nvim/api/private/helpers.h | 32 | ||||
| -rw-r--r-- | src/nvim/api/tabpage.c | 132 | ||||
| -rw-r--r-- | src/nvim/api/ui.c | 617 | ||||
| -rw-r--r-- | src/nvim/api/ui_events.in.h | 130 | ||||
| -rw-r--r-- | src/nvim/api/vim.c | 1702 | ||||
| -rw-r--r-- | src/nvim/api/vim.h | 4 | ||||
| -rw-r--r-- | src/nvim/api/window.c | 276 |
15 files changed, 3740 insertions, 962 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 55b535c78c..5df0f0bb47 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> @@ -10,11 +13,11 @@ #include "nvim/api/private/defs.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/misc1.h" -#include "nvim/misc2.h" #include "nvim/ex_cmds.h" #include "nvim/mark.h" #include "nvim/fileio.h" @@ -22,17 +25,35 @@ #include "nvim/syntax.h" #include "nvim/window.h" #include "nvim/undo.h" +#include "nvim/ex_docmd.h" +#include "nvim/buffer_updates.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" #endif + +/// \defgroup api-buffer +/// +/// Unloaded Buffers:~ +/// +/// Buffers may be unloaded by the |:bunload| command or the buffer's +/// |'bufhidden'| option. When a buffer is unloaded its file contents are freed +/// from memory and vim cannot operate on the buffer lines until it is reloaded +/// (usually by opening the buffer again in a new window). API methods such as +/// |nvim_buf_get_lines()| and |nvim_buf_line_count()| will be affected. +/// +/// You can use |nvim_buf_is_loaded()| or |nvim_buf_line_count()| to check +/// whether a buffer is loaded. + + /// Gets the buffer line count /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The line count -Integer buffer_line_count(Buffer buffer, Error *err) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Line count, or 0 for unloaded buffer. |api-buffer| +Integer nvim_buf_line_count(Buffer buffer, Error *err) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -40,29 +61,34 @@ Integer buffer_line_count(Buffer buffer, Error *err) return 0; } + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return 0; + } + return buf->b_ml.ml_line_count; } /// Gets a buffer line /// -/// @deprecated use buffer_get_lines instead. +/// @deprecated use nvim_buf_get_lines instead. /// for positive indices (including 0) use -/// "buffer_get_lines(buffer, index, index+1, true)" +/// "nvim_buf_get_lines(buffer, index, index+1, true)" /// for negative indices use -/// "buffer_get_lines(buffer, index-1, index, true)" +/// "nvim_buf_get_lines(buffer, index-1, index, true)" /// -/// @param buffer The buffer handle -/// @param index The line index -/// @param[out] err Details of an error that may have occurred -/// @return The line string +/// @param buffer Buffer handle +/// @param index Line index +/// @param[out] err Error details, if any +/// @return Line string String buffer_get_line(Buffer buffer, Integer index, Error *err) { String rv = { .size = 0 }; index = convert_index(index); - Array slice = buffer_get_lines(buffer, index, index+1, true, err); + 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; } @@ -71,89 +97,143 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) return rv; } +/// Activate updates from this buffer to the current channel. +/// +/// @param buffer The buffer handle +/// @param send_buffer Set to true if the initial notification should contain +/// the whole buffer. If so, the first notification will be a +/// `nvim_buf_lines_event`. Otherwise, the first notification will be +/// a `nvim_buf_changedtick_event` +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Details of an error that may have occurred +/// @return False when updates couldn't be enabled because the buffer isn't +/// loaded or `opts` contained an invalid key; otherwise True. +Boolean nvim_buf_attach(uint64_t channel_id, + Buffer buffer, + Boolean send_buffer, + Dictionary opts, + Error *err) + FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY +{ + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "dict isn't empty"); + return false; + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return false; + } + + return buf_updates_register(buf, channel_id, send_buffer); +} +// +/// Deactivate updates from this buffer to the current channel. +/// +/// @param buffer The buffer handle +/// @param[out] err Details of an error that may have occurred +/// @return False when updates couldn't be disabled because the buffer +/// isn't loaded; otherwise True. +Boolean nvim_buf_detach(uint64_t channel_id, + Buffer buffer, + Error *err) + FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return false; + } + + buf_updates_unregister(buf, channel_id); + return true; +} + /// Sets a buffer line /// -/// @deprecated use buffer_set_lines instead. +/// @deprecated use nvim_buf_set_lines instead. /// for positive indices use -/// "buffer_set_lines(buffer, index, index+1, true, [line])" +/// "nvim_buf_set_lines(buffer, index, index+1, true, [line])" /// for negative indices use -/// "buffer_set_lines(buffer, index-1, index, true, [line])" +/// "nvim_buf_set_lines(buffer, index-1, index, true, [line])" /// -/// @param buffer The buffer handle -/// @param index The line index -/// @param line The new line. -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param index Line index +/// @param line Contents of the new line +/// @param[out] err Error details, if any void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) { Object l = STRING_OBJ(line); Array array = { .items = &l, .size = 1 }; index = convert_index(index); - buffer_set_lines(buffer, index, index+1, true, array, err); + nvim_buf_set_lines(0, buffer, index, index+1, true, array, err); } /// Deletes a buffer line /// -/// @deprecated use buffer_set_lines instead. +/// @deprecated use nvim_buf_set_lines instead. /// for positive indices use -/// "buffer_set_lines(buffer, index, index+1, true, [])" +/// "nvim_buf_set_lines(buffer, index, index+1, true, [])" /// for negative indices use -/// "buffer_set_lines(buffer, index-1, index, true, [])" -/// @param buffer The buffer handle -/// @param index The line index -/// @param[out] err Details of an error that may have occurred +/// "nvim_buf_set_lines(buffer, index-1, index, true, [])" +/// @param buffer buffer handle +/// @param index line index +/// @param[out] err Error details, if any void buffer_del_line(Buffer buffer, Integer index, Error *err) { Array array = ARRAY_DICT_INIT; index = convert_index(index); - buffer_set_lines(buffer, index, index+1, true, array, err); + nvim_buf_set_lines(0, buffer, index, index+1, true, array, err); } /// Retrieves a line range from the buffer /// -/// @deprecated use buffer_get_lines(buffer, newstart, newend, false) +/// @deprecated use nvim_buf_get_lines(buffer, newstart, newend, false) /// where newstart = start + int(not include_start) - int(start < 0) /// newend = end + int(include_end) - int(end < 0) /// int(bool) = 1 if bool is true else 0 -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index -/// @param include_start True if the slice includes the `start` parameter -/// @param include_end True if the slice includes the `end` parameter -/// @param[out] err Details of an error that may have occurred -/// @return An array of lines +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param[out] err Error details, if any +/// @return Array of lines ArrayOf(String) buffer_get_line_slice(Buffer buffer, - Integer start, - Integer end, - Boolean include_start, - Boolean include_end, - Error *err) + Integer start, + Integer end, + Boolean include_start, + Boolean include_end, + Error *err) { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - return buffer_get_lines(buffer, start , end, false, err); + return nvim_buf_get_lines(0, buffer, start , end, false, err); } - -/// Retrieves a line range from the buffer +/// Gets a line-range from the buffer. /// /// Indexing is zero-based, end-exclusive. Negative indices are interpreted -/// as length+1+index, i e -1 refers to the index past the end. So to get the -/// last element set start=-2 and end=-1. +/// as length+1+index: -1 refers to the index past the end. So to get the +/// last element use start=-2 and end=-1. /// /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index (exclusive) -/// @param strict_indexing whether out-of-bounds should be an error. -/// @param[out] err Details of an error that may have occurred -/// @return An array of lines -ArrayOf(String) buffer_get_lines(Buffer buffer, - Integer start, - Integer end, - Boolean strict_indexing, - Error *err) +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index (exclusive) +/// @param strict_indexing Whether out-of-bounds should be an error. +/// @param[out] err Error details, if any +/// @return Array of lines, or empty array for unloaded buffer. +ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, + Buffer buffer, + Integer start, + Integer end, + Boolean strict_indexing, + Error *err) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -162,12 +242,17 @@ ArrayOf(String) buffer_get_lines(Buffer buffer, return rv; } + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return rv; + } + bool oob = false; start = normalize_index(buf, start, &oob); 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; } @@ -179,25 +264,13 @@ ArrayOf(String) buffer_get_lines(Buffer buffer, rv.size = (size_t)(end - start); rv.items = xcalloc(sizeof(Object), rv.size); - 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")); - goto end; - } - - 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. - strchrsub(str.data.string.data, '\n', '\0'); - - rv.items[i] = str; + if (!buf_collect_lines(buf, rv.size, start, + (channel_id != VIML_INTERNAL_CALL), &rv, err)) { + goto end; } end: - if (err->set) { + if (ERROR_SET(err)) { for (size_t i = 0; i < rv.size; i++) { xfree(rv.items[i].data.string.data); } @@ -212,57 +285,59 @@ end: /// Replaces a line range on the buffer /// -/// @deprecated use buffer_set_lines(buffer, newstart, newend, false, lines) +/// @deprecated use nvim_buf_set_lines(buffer, newstart, newend, false, lines) /// where newstart = start + int(not include_start) + int(start < 0) /// newend = end + int(include_end) + int(end < 0) /// int(bool) = 1 if bool is true else 0 /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index -/// @param include_start True if the slice includes the `start` parameter -/// @param include_end True if the slice includes the `end` parameter -/// @param replacement An array of lines to use as replacement(A 0-length -// array will simply delete the line range) -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param replacement Array of lines to use as replacement (0-length +// array will delete the line range) +/// @param[out] err Error details, if any void buffer_set_line_slice(Buffer buffer, - Integer start, - Integer end, - Boolean include_start, - Boolean include_end, - ArrayOf(String) replacement, - Error *err) + Integer start, + Integer end, + Boolean include_start, + Boolean include_end, + ArrayOf(String) replacement, // NOLINT + Error *err) { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - buffer_set_lines(buffer, start, end, false, replacement, err); + nvim_buf_set_lines(0, buffer, start, end, false, replacement, err); } -/// Replaces line range on the buffer +/// Sets (replaces) a line-range in the buffer. /// /// Indexing is zero-based, end-exclusive. Negative indices are interpreted -/// as length+1+index, i e -1 refers to the index past the end. So to change -/// or delete the last element set start=-2 and end=-1. +/// as length+1+index: -1 refers to the index past the end. So to change +/// or delete the last element use start=-2 and end=-1. /// -/// To insert lines at a given index, set both start and end to the same index. -/// To delete a range of lines, set replacement to an empty array. +/// To insert lines at a given index, set `start` and `end` to the same index. +/// To delete a range of lines, set `replacement` to an empty array. /// /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index (exclusive) -/// @param strict_indexing whether out-of-bounds should be an error. -/// @param replacement An array of lines to use as replacement -/// @param[out] err Details of an error that may have occurred -void buffer_set_lines(Buffer buffer, - Integer start, - Integer end, - Boolean strict_indexing, - ArrayOf(String) replacement, - Error *err) +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index (exclusive) +/// @param strict_indexing Whether out-of-bounds should be an error. +/// @param replacement Array of lines to use as replacement +/// @param[out] err Error details, if any +void nvim_buf_set_lines(uint64_t channel_id, + Buffer buffer, + Integer start, + Integer end, + Boolean strict_indexing, + ArrayOf(String) replacement, // NOLINT + Error *err) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -275,54 +350,58 @@ void buffer_set_lines(Buffer buffer, 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; } - buf_T *save_curbuf = NULL; + 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; size_t old_len = (size_t)(end - start); - ssize_t extra = 0; // lines added to text, can be negative + ptrdiff_t extra = 0; // lines added to text, can be negative 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; - } + const String l = replacement.items[i].data.string; - 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') { - 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, 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; } @@ -332,13 +411,13 @@ void buffer_set_lines(Buffer buffer, 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; } } - if ((ssize_t)to_delete > 0) { - extra -= (ssize_t)to_delete; + if (to_delete > 0) { + extra -= (ptrdiff_t)to_delete; } // For as long as possible, replace the existing old_len with the @@ -348,13 +427,13 @@ void buffer_set_lines(Buffer buffer, 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 @@ -366,13 +445,13 @@ void buffer_set_lines(Buffer buffer, 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; } @@ -386,14 +465,18 @@ void buffer_set_lines(Buffer buffer, // changed range, and move any in the remainder of the buffer. // Only adjust marks if we managed to switch to a window that holds // the buffer, otherwise line numbers will be invalid. - if (save_curbuf == NULL) { - mark_adjust((linenr_T)start, (linenr_T)(end - 1), MAXLNUM, extra); + if (save_curbuf.br_buf == NULL) { + mark_adjust((linenr_T)start, + (linenr_T)(end - 1), + MAXLNUM, + (long)extra, + false); } - changed_lines((linenr_T)start, 0, (linenr_T)end, extra); + changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); - if (buf == curbuf) { - fix_cursor((linenr_T)start, (linenr_T)end, extra); + if (save_curbuf.br_buf == NULL) { + fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); } end: @@ -402,17 +485,53 @@ end: } xfree(lines); - restore_win_for_buf(save_curwin, save_curtab, save_curbuf); + restore_win_for_buf(save_curwin, save_curtab, &save_curbuf); try_end(err); } +/// Returns the byte offset for a line. +/// +/// Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte. +/// 'fileformat' and 'fileencoding' are ignored. The line index just after the +/// last line gives the total byte-count of the buffer. A final EOL byte is +/// counted if it would be written, see 'eol'. +/// +/// Unlike |line2byte()|, throws error for out-of-bounds indexing. +/// Returns -1 for unloaded buffer. +/// +/// @param buffer Buffer handle +/// @param index Line index +/// @param[out] err Error details, if any +/// @return Integer byte offset, or -1 for unloaded buffer. +Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err) + FUNC_API_SINCE(5) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + // return sentinel value if the buffer isn't loaded + if (buf->b_ml.ml_mfp == NULL) { + return -1; + } + + if (index < 0 || index > buf->b_ml.ml_line_count) { + api_set_error(err, kErrorTypeValidation, "Index out of bounds"); + return 0; + } + + return ml_find_line_or_offset(buf, (int)index+1, NULL, true); +} + /// Gets a buffer-scoped (b:) variable. /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object buffer_get_var(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Variable name +/// @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) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -423,13 +542,127 @@ Object buffer_get_var(Buffer buffer, String name, Error *err) return dict_get_value(buf->b_vars, name, 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) +{ + const buf_T *const buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return -1; + } + + return buf_get_changedtick(buf); +} + +/// Gets a list of buffer-local |mapping| definitions. +/// +/// @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. +/// The "buffer" key holds the associated buffer handle. +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); +} + +/// Gets a map of buffer-local |user-commands|. +/// +/// @param buffer Buffer handle. +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Error details, if any. +/// +/// @returns Map of maps describing commands. +Dictionary nvim_buf_get_commands(Buffer buffer, Dictionary opts, Error *err) + FUNC_API_SINCE(4) +{ + bool global = (buffer == -1); + bool builtin = false; + + for (size_t i = 0; i < opts.size; i++) { + String k = opts.items[i].key; + Object v = opts.items[i].value; + if (!strequal("builtin", k.data)) { + api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + return (Dictionary)ARRAY_DICT_INIT; + } + if (strequal("builtin", k.data)) { + builtin = v.data.boolean; + } + } + + if (global) { + if (builtin) { + api_set_error(err, kErrorTypeValidation, "builtin=true not implemented"); + return (Dictionary)ARRAY_DICT_INIT; + } + return commands_array(NULL); + } + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (builtin || !buf) { + return (Dictionary)ARRAY_DICT_INIT; + } + return commands_array(buf); +} + /// Sets a buffer-scoped (b:) variable /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param buffer Buffer handle +/// @param name Variable name +/// @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) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return; + } + + dict_set_var(buf->b_vars, name, value, false, false, err); +} + +/// Removes a buffer-scoped (b:) variable +/// +/// @param buffer Buffer handle +/// @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) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return; + } + + dict_set_var(buf->b_vars, name, NIL, true, false, err); +} + +/// Sets a buffer-scoped (b:) variable +/// +/// @deprecated +/// +/// @param buffer Buffer handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @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`. @@ -441,18 +674,17 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(buf->b_vars, name, value, false, err); + return dict_set_var(buf->b_vars, name, value, false, true, err); } /// Removes a buffer-scoped (b:) variable /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object buffer_del_var(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -461,16 +693,18 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(buf->b_vars, name, NIL, true, err); + return dict_set_var(buf->b_vars, name, NIL, true, true, err); } + /// Gets a buffer option value /// -/// @param buffer The buffer handle -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value -Object buffer_get_option(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_buf_get_option(Buffer buffer, String name, Error *err) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -481,14 +715,16 @@ Object buffer_get_option(Buffer buffer, String name, Error *err) return get_option_from(buf, SREQ_BUF, name, err); } -/// Sets a buffer option value. Passing 'nil' as value deletes the option(only +/// Sets a buffer option value. Passing 'nil' as value deletes the option (only /// works if there's a global fallback) /// -/// @param buffer The buffer handle -/// @param name The option name -/// @param value The option value -/// @param[out] err Details of an error that may have occurred -void buffer_set_option(Buffer buffer, String name, Object value, Error *err) +/// @param buffer Buffer handle +/// @param name Option name +/// @param value Option value +/// @param[out] err Error details, if any +void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, + String name, Object value, Error *err) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -496,15 +732,20 @@ void buffer_set_option(Buffer buffer, String name, Object value, Error *err) return; } - set_option_to(buf, SREQ_BUF, name, value, err); + set_option_to(channel_id, buf, SREQ_BUF, name, value, err); } /// Gets the buffer number /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer number -Integer buffer_get_number(Buffer buffer, Error *err) +/// @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_DEPRECATED_SINCE(2) { Integer rv = 0; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -518,10 +759,11 @@ Integer buffer_get_number(Buffer buffer, Error *err) /// Gets the full file name for the buffer /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer name -String buffer_get_name(Buffer buffer, Error *err) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Buffer name +String nvim_buf_get_name(Buffer buffer, Error *err) + FUNC_API_SINCE(1) { String rv = STRING_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -535,10 +777,11 @@ String buffer_get_name(Buffer buffer, Error *err) /// Sets the full file name for a buffer /// -/// @param buffer The buffer handle -/// @param name The buffer name -/// @param[out] err Details of an error that may have occurred -void buffer_set_name(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @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) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -559,29 +802,49 @@ void buffer_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"); } } -/// Checks if a buffer is valid +/// Checks if a buffer is valid and loaded. See |api-buffer| for more info +/// about unloaded buffers. /// -/// @param buffer The buffer handle -/// @return true if the buffer is valid, false otherwise -Boolean buffer_is_valid(Buffer buffer) +/// @param buffer Buffer handle +/// @return true if the buffer is valid and loaded, false otherwise. +Boolean nvim_buf_is_loaded(Buffer buffer) + FUNC_API_SINCE(5) +{ + Error stub = ERROR_INIT; + buf_T *buf = find_buffer_by_handle(buffer, &stub); + api_clear_error(&stub); + return buf && buf->b_ml.ml_mfp != NULL; +} + +/// Checks if a buffer is valid. +/// +/// @note Even if a buffer is valid it may have been unloaded. See |api-buffer| +/// for more info about unloaded buffers. +/// +/// @param buffer Buffer handle +/// @return true if the buffer is valid, false otherwise. +Boolean nvim_buf_is_valid(Buffer buffer) + 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 /// -/// @deprecated use buffer_set_lines(buffer, lnum, lnum, true, lines) +/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines) /// -/// @param buffer The buffer handle -/// @param lnum Insert the lines after `lnum`. If negative, it will append -/// to the end of the buffer. -/// @param lines An array of lines -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param lnum Insert the lines after `lnum`. If negative, appends to +/// the end of the buffer. +/// @param lines Array of lines +/// @param[out] err Error details, if any void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, @@ -589,16 +852,17 @@ void buffer_insert(Buffer buffer, { // "lnum" will be the index of the line after inserting, // no matter if it is negative or not - buffer_set_lines(buffer, lnum, lnum, true, lines, err); + nvim_buf_set_lines(0, buffer, lnum, lnum, true, lines, err); } /// Return a tuple (row,col) representing the position of the named mark /// -/// @param buffer The buffer handle -/// @param name The mark's name -/// @param[out] err Details of an error that may have occurred -/// @return The (row, col) tuple -ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err) +/// @param buffer Buffer handle +/// @param name Mark name +/// @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) { Array rv = ARRAY_DICT_INIT; buf_T *buf = find_buffer_by_handle(buffer, err); @@ -608,25 +872,26 @@ ArrayOf(Integer, 2) buffer_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; } pos_T *posp; - buf_T *savebuf; char mark = *name.data; try_start(); - switch_buffer(&savebuf, buf); + bufref_T save_buf; + switch_buffer(&save_buf, buf); posp = getmark(mark, false); - restore_buffer(savebuf); + restore_buffer(&save_buf); if (try_end(err)) { return rv; } if (posp == NULL) { - api_set_error(err, Validation, _("Invalid mark name")); + api_set_error(err, kErrorTypeValidation, "Invalid mark name"); return rv; } @@ -638,41 +903,42 @@ ArrayOf(Integer, 2) buffer_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 -/// highlight to a buffer. Unlike matchaddpos() highlights follow changes to +/// 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 -/// buffer_clear_highlight. If the highlight never will be manually deleted -/// pass in -1 for "src_id". +/// Namespaces are used for batch deletion/updating of a set of highlights. To +/// create a namespace, use |nvim_create_namespace| which returns a namespace +/// id. Pass it in to this function as `ns_id` to add highlights to the +/// namespace. All highlights in the same namespace can then be cleared with +/// single call to |nvim_buf_clear_namespace|. If the highlight never will be +/// deleted by an API call, pass `ns_id = -1`. /// -/// 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. +/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the +/// highlight, the allocated id is then returned. If `hl_group` is the empty +/// string no highlight is added, but a new `ns_id` is still returned. This is +/// supported for backwards compatibility, new code should use +/// |nvim_create_namespace| to create a new empty namespace. /// -/// @param buffer The 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 The line to highlight -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, -/// or -1 to highlight to end of line -/// @param[out] err Details of an error that may have occurred -/// @return The src_id that was used -Integer buffer_add_highlight(Buffer buffer, - Integer src_id, - String hl_group, - Integer line, - Integer col_start, - Integer col_end, - Error *err) +/// @param buffer Buffer handle +/// @param ns_id namespace to use or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line Line to highlight (zero-indexed) +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Error details, if any +/// @return The ns_id that was used +Integer nvim_buf_add_highlight(Buffer buffer, + Integer ns_id, + String hl_group, + Integer line, + Integer col_start, + Integer col_end, + Error *err) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -680,39 +946,44 @@ Integer buffer_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); - 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; + int hlg_id = 0; + if (hl_group.size > 0) { + hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size); + } + + ns_id = bufhl_add_hl(buf, (int)ns_id, hlg_id, (linenr_T)line+1, + (colnr_T)col_start+1, (colnr_T)col_end); + return ns_id; } -/// Clears highlights from a given source group and a range of lines +/// Clears namespaced objects, highlights and virtual text, from a line range /// -/// To clear a source group in the entire buffer, pass in 1 and -1 to +/// To clear the namespace in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. /// -/// @param buffer The buffer handle -/// @param src_id Highlight source group to clear, or -1 to clear all groups. +/// @param buffer Buffer handle +/// @param ns_id Namespace to clear, or -1 to clear all namespaces. /// @param line_start Start of range of lines to clear -/// @param line_end End of range of lines to clear (exclusive) -/// or -1 to clear to end of file. -/// @param[out] err Details of an error that may have occurred -void buffer_clear_highlight(Buffer buffer, - Integer src_id, - Integer line_start, - Integer line_end, - Error *err) +/// @param line_end End of range of lines to clear (exclusive) or -1 to clear +/// to end of buffer. +/// @param[out] err Error details, if any +void nvim_buf_clear_namespace(Buffer buffer, + Integer ns_id, + Integer line_start, + Integer line_end, + Error *err) + FUNC_API_SINCE(5) { buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { @@ -720,14 +991,122 @@ void buffer_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) { line_end = MAXLNUM; } - bufhl_clear_line_range(buf, (int)src_id, (int)line_start+1, (int)line_end); + bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end); +} + +/// Clears highlights and virtual text from namespace and range of lines +/// +/// @deprecated use |nvim_buf_clear_namespace|. +/// +/// @param buffer Buffer handle +/// @param ns_id Namespace to clear, or -1 to clear all. +/// @param line_start Start of range of lines to clear +/// @param line_end End of range of lines to clear (exclusive) or -1 to clear +/// to end of file. +/// @param[out] err Error details, if any +void nvim_buf_clear_highlight(Buffer buffer, + Integer ns_id, + Integer line_start, + Integer line_end, + Error *err) + FUNC_API_SINCE(1) +{ + nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err); +} + + +/// Set the virtual text (annotation) for a buffer line. +/// +/// By default (and currently the only option) the text will be placed after +/// the buffer text. Virtual text will never cause reflow, rather virtual +/// text will be truncated at the end of the screen line. The virtual text will +/// begin one cell (|lcs-eol| or space) after the ordinary text. +/// +/// Namespaces are used to support batch deletion/updating of virtual text. +/// To create a namespace, use |nvim_create_namespace|. Virtual text is +/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for +/// both virtual text and highlights added by |nvim_buf_add_highlight|, both +/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the +/// virtual text never will be cleared by an API call, pass `ns_id = -1`. +/// +/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the +/// virtual text, the allocated id is then returned. +/// +/// @param buffer Buffer handle +/// @param ns_id Namespace to use or 0 to create a namespace, +/// or -1 for a ungrouped annotation +/// @param line Line to annotate with virtual text (zero-indexed) +/// @param chunks A list of [text, hl_group] arrays, each representing a +/// text chunk with specified highlight. `hl_group` element +/// can be omitted for no highlight. +/// @param opts Optional parameters. Currently not used. +/// @param[out] err Error details, if any +/// @return The ns_id that was used +Integer nvim_buf_set_virtual_text(Buffer buffer, + Integer ns_id, + Integer line, + Array chunks, + Dictionary opts, + Error *err) + FUNC_API_SINCE(5) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return 0; + } + + if (line < 0 || line >= MAXLNUM) { + api_set_error(err, kErrorTypeValidation, "Line number outside range"); + return 0; + } + + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return 0; + } + + VirtText virt_text = KV_INITIAL_VALUE; + for (size_t i = 0; i < chunks.size; i++) { + if (chunks.items[i].type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "Chunk is not an array"); + goto free_exit; + } + Array chunk = chunks.items[i].data.array; + if (chunk.size == 0 || chunk.size > 2 + || chunk.items[0].type != kObjectTypeString + || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) { + api_set_error(err, kErrorTypeValidation, + "Chunk is not an array with one or two strings"); + goto free_exit; + } + + String str = chunk.items[0].data.string; + char *text = transstr(str.size > 0 ? str.data : ""); // allocates + + int hl_id = 0; + if (chunk.size == 2) { + String hl = chunk.items[1].data.string; + if (hl.size > 0) { + hl_id = syn_check_group((char_u *)hl.data, (int)hl.size); + } + } + kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id })); + } + + ns_id = bufhl_add_virt_text(buf, (int)ns_id, (linenr_T)line+1, + virt_text); + return ns_id; + +free_exit: + kv_destroy(virt_text); + return 0; } // Check if deleting lines made the cursor position invalid. diff --git a/src/nvim/api/dispatch_deprecated.lua b/src/nvim/api/dispatch_deprecated.lua new file mode 100644 index 0000000000..5650a77ac0 --- /dev/null +++ b/src/nvim/api/dispatch_deprecated.lua @@ -0,0 +1,69 @@ +local deprecated_aliases = { + nvim_buf_add_highlight="buffer_add_highlight", + nvim_buf_clear_highlight="buffer_clear_highlight", + nvim_buf_get_lines="buffer_get_lines", + nvim_buf_get_mark="buffer_get_mark", + nvim_buf_get_name="buffer_get_name", + nvim_buf_get_number="buffer_get_number", + nvim_buf_get_option="buffer_get_option", + nvim_buf_get_var="buffer_get_var", + nvim_buf_is_valid="buffer_is_valid", + nvim_buf_line_count="buffer_line_count", + nvim_buf_set_lines="buffer_set_lines", + nvim_buf_set_name="buffer_set_name", + nvim_buf_set_option="buffer_set_option", + nvim_call_function="vim_call_function", + nvim_command="vim_command", + nvim_command_output="vim_command_output", + nvim_del_current_line="vim_del_current_line", + nvim_err_write="vim_err_write", + nvim_err_writeln="vim_report_error", + nvim_eval="vim_eval", + nvim_feedkeys="vim_feedkeys", + nvim_get_api_info="vim_get_api_info", + nvim_get_color_by_name="vim_name_to_color", + nvim_get_color_map="vim_get_color_map", + nvim_get_current_buf="vim_get_current_buffer", + nvim_get_current_line="vim_get_current_line", + nvim_get_current_tabpage="vim_get_current_tabpage", + nvim_get_current_win="vim_get_current_window", + nvim_get_option="vim_get_option", + nvim_get_var="vim_get_var", + nvim_get_vvar="vim_get_vvar", + nvim_input="vim_input", + nvim_list_bufs="vim_get_buffers", + nvim_list_runtime_paths="vim_list_runtime_paths", + nvim_list_tabpages="vim_get_tabpages", + nvim_list_wins="vim_get_windows", + nvim_out_write="vim_out_write", + nvim_replace_termcodes="vim_replace_termcodes", + nvim_set_current_buf="vim_set_current_buffer", + nvim_set_current_dir="vim_change_directory", + nvim_set_current_line="vim_set_current_line", + nvim_set_current_tabpage="vim_set_current_tabpage", + nvim_set_current_win="vim_set_current_window", + nvim_set_option="vim_set_option", + nvim_strwidth="vim_strwidth", + nvim_subscribe="vim_subscribe", + nvim_tabpage_get_var="tabpage_get_var", + nvim_tabpage_get_win="tabpage_get_window", + nvim_tabpage_is_valid="tabpage_is_valid", + nvim_tabpage_list_wins="tabpage_get_windows", + nvim_ui_detach="ui_detach", + nvim_ui_try_resize="ui_try_resize", + nvim_unsubscribe="vim_unsubscribe", + nvim_win_get_buf="window_get_buffer", + nvim_win_get_cursor="window_get_cursor", + nvim_win_get_height="window_get_height", + nvim_win_get_option="window_get_option", + nvim_win_get_position="window_get_position", + nvim_win_get_tabpage="window_get_tabpage", + nvim_win_get_var="window_get_var", + nvim_win_get_width="window_get_width", + nvim_win_is_valid="window_is_valid", + nvim_win_set_cursor="window_set_cursor", + nvim_win_set_height="window_set_height", + nvim_win_set_option="window_set_option", + nvim_win_set_width="window_set_width", +} +return deprecated_aliases diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 5fb95a163f..feca140547 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -5,11 +5,16 @@ #include <stdbool.h> #include <string.h> +#include "nvim/func_attr.h" +#include "nvim/types.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 REMOTE_TYPE(type) typedef uint64_t type +#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 @@ -18,6 +23,7 @@ // Basic types typedef enum { + kErrorTypeNone = -1, kErrorTypeException, kErrorTypeValidation } ErrorType; @@ -31,10 +37,31 @@ typedef enum { /// Used as the message ID of notifications. #define NO_RESPONSE 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; @@ -71,24 +98,22 @@ 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 { ObjectType type; union { - Buffer buffer; - Window window; - Tabpage tabpage; Boolean boolean; Integer integer; Float floating; diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c new file mode 100644 index 0000000000..8492225a69 --- /dev/null +++ b/src/nvim/api/private/dispatch.c @@ -0,0 +1,52 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <inttypes.h> +#include <stdbool.h> +#include <stdint.h> +#include <assert.h> +#include <msgpack.h> + +#include "nvim/map.h" +#include "nvim/log.h" +#include "nvim/vim.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/api/private/dispatch.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/private/defs.h" + +#include "nvim/api/buffer.h" +#include "nvim/api/tabpage.h" +#include "nvim/api/ui.h" +#include "nvim/api/vim.h" +#include "nvim/api/window.h" + +static Map(String, MsgpackRpcRequestHandler) *methods = NULL; + +static void msgpack_rpc_add_method_handler(String method, + MsgpackRpcRequestHandler handler) +{ + map_put(String, MsgpackRpcRequestHandler)(methods, method, handler); +} + +/// @param name API method name +/// @param name_len name size (includes terminating NUL) +MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, + size_t name_len, + Error *error) +{ + String m = { .data = (char *)name, .size = name_len }; + MsgpackRpcRequestHandler rv = + map_get(String, MsgpackRpcRequestHandler)(methods, m); + + if (!rv.fn) { + api_set_error(error, kErrorTypeException, "Invalid method: %.*s", + m.size > 0 ? (int)m.size : (int)sizeof("<empty>"), + m.size > 0 ? m.data : "<empty>"); + } + return rv; +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "api/private/dispatch_wrappers.generated.h" +#endif diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h new file mode 100644 index 0000000000..39aabd708a --- /dev/null +++ b/src/nvim/api/private/dispatch.h @@ -0,0 +1,23 @@ +#ifndef NVIM_API_PRIVATE_DISPATCH_H +#define NVIM_API_PRIVATE_DISPATCH_H + +#include "nvim/api/private/defs.h" + +typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, + Array args, + Error *error); + +/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores +/// functions of this type. +typedef struct { + ApiDispatchWrapper fn; + bool async; // function is always safe to run immediately instead of being + // put in a request queue for handling when nvim waits for input. +} MsgpackRpcRequestHandler; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/private/dispatch.h.generated.h" +# include "api/private/dispatch_wrappers.h.generated.h" +#endif + +#endif // NVIM_API_PRIVATE_DISPATCH_H diff --git a/src/nvim/api/private/handle.c b/src/nvim/api/private/handle.c index 69df7294ad..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> @@ -5,30 +8,26 @@ #include "nvim/map.h" #include "nvim/api/private/handle.h" -#define HANDLE_INIT(name) name##_handles = pmap_new(uint64_t)() +#define HANDLE_INIT(name) name##_handles = pmap_new(handle_T)() #define HANDLE_IMPL(type, name) \ - static PMap(uint64_t) *name##_handles = NULL; \ + static PMap(handle_T) *name##_handles = NULL; /* NOLINT */ \ \ - type *handle_get_##name(uint64_t handle) \ + type *handle_get_##name(handle_T handle) \ { \ - return pmap_get(uint64_t)(name##_handles, handle); \ + return pmap_get(handle_T)(name##_handles, handle); \ } \ \ void handle_register_##name(type *name) \ { \ - assert(!name->handle); \ - name->handle = next_handle++; \ - pmap_put(uint64_t)(name##_handles, name->handle, name); \ + pmap_put(handle_T)(name##_handles, name->handle, name); \ } \ \ void handle_unregister_##name(type *name) \ { \ - pmap_del(uint64_t)(name##_handles, name->handle); \ + pmap_del(handle_T)(name##_handles, name->handle); \ } -static uint64_t next_handle = 1; - HANDLE_IMPL(buf_T, buffer) HANDLE_IMPL(win_T, window) HANDLE_IMPL(tabpage_T, tabpage) diff --git a/src/nvim/api/private/handle.h b/src/nvim/api/private/handle.h index 804e266dc3..26e9dc3314 100644 --- a/src/nvim/api/private/handle.h +++ b/src/nvim/api/private/handle.h @@ -3,14 +3,18 @@ #include "nvim/vim.h" #include "nvim/buffer_defs.h" +#include "nvim/api/private/defs.h" #define HANDLE_DECLS(type, name) \ - type *handle_get_##name(uint64_t handle); \ + type *handle_get_##name(handle_T handle); \ void handle_register_##name(type *name); \ void handle_unregister_##name(type *name); +// handle_get_buffer handle_register_buffer, handle_unregister_buffer HANDLE_DECLS(buf_T, buffer) +// handle_get_window handle_register_window, handle_unregister_window HANDLE_DECLS(win_T, window) +// handle_get_tabpage handle_register_tabpage, handle_unregister_tabpage HANDLE_DECLS(tabpage_T, tabpage) void handle_init(void); diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index c88bf2127a..82c9a1da67 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> @@ -7,18 +10,24 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #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" +#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/map_defs.h" #include "nvim/map.h" #include "nvim/option.h" #include "nvim/option_defs.h" -#include "nvim/eval/typval_encode.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 { @@ -27,9 +36,75 @@ 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; @@ -42,20 +117,20 @@ 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 - // cycle + // Set by emsg(), affects aborting(). See also enter_cleanup(). 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; @@ -63,19 +138,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 @@ -85,18 +159,17 @@ bool try_end(Error *err) /// @param[out] err Details of an error that may have occurred Object dict_get_value(dict_T *dict, String key, Error *err) { - hashitem_T *hi = hash_find(&dict->dv_hashtab, (uint8_t *) key.data); + dictitem_T *const di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); - if (HASHITEM_EMPTY(hi)) { - api_set_error(err, Validation, _("Key not found")); - return (Object) OBJECT_INIT; + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); + return (Object)OBJECT_INIT; } - dictitem_T *di = dict_lookup(hi); return vim_to_object(&di->di_tv); } -/// Set a value in a dict. Objects are recursively expanded into their +/// Set a value in a scope dict. Objects are recursively expanded into their /// vimscript equivalents. /// /// @param dict The vimscript dict @@ -104,42 +177,50 @@ Object dict_get_value(dict_T *dict, String key, Error *err) /// @param value The new value /// @param del Delete key in place of setting it. Argument `value` is ignored in /// this case. +/// @param retval If true the old value will be converted and returned. /// @param[out] err Details of an error that may have occurred -/// @return the old value, if any -Object dict_set_value(dict_T *dict, String key, Object value, bool del, - Error *err) +/// @return The old value if `retval` is true and the key was present, else NIL +Object dict_set_var(dict_T *dict, String key, Object value, bool del, + bool retval, Error *err) { Object rv = OBJECT_INIT; + dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size); - if (dict->dv_lock) { - api_set_error(err, Exception, _("Dictionary is locked")); + if (di != NULL) { + if (di->di_flags & DI_FLAGS_RO) { + api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data); + return rv; + } else if (di->di_flags & DI_FLAGS_LOCK) { + api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data); + return rv; + } else if (del && (di->di_flags & DI_FLAGS_FIX)) { + api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data); + return rv; + } + } else if (dict->dv_lock) { + api_set_error(err, kErrorTypeException, "Dictionary is locked"); return rv; - } - - if (key.size == 0) { - api_set_error(err, Validation, _("Empty dictionary keys aren't allowed")); + } else if (key.size == 0) { + api_set_error(err, kErrorTypeValidation, "Key name is empty"); return rv; - } - - if (key.size > INT_MAX) { - api_set_error(err, Validation, _("Key length is too high")); + } else if (key.size > INT_MAX) { + api_set_error(err, kErrorTypeValidation, "Key name is too long"); return rv; } - dictitem_T *di = dict_find(dict, (uint8_t *)key.data, (int)key.size); - if (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 not found: %s", + key.data); } else { // Return the old value - rv = vim_to_object(&di->di_tv); + if (retval) { + rv = vim_to_object(&di->di_tv); + } // Delete the entry - hashitem_T *hi = hash_find(&dict->dv_hashtab, di->di_key); - hash_remove(&dict->dv_hashtab, hi); - dictitem_free(di); + tv_dict_item_remove(dict, di); } } else { // Update the key @@ -152,18 +233,20 @@ Object dict_set_value(dict_T *dict, String key, Object value, bool del, if (di == NULL) { // Need to create an entry - di = dictitem_alloc((uint8_t *) key.data); - dict_add(dict, di); + di = tv_dict_item_alloc_len(key.data, key.size); + tv_dict_add(dict, di); } else { // Return the old value - rv = vim_to_object(&di->di_tv); - clear_tv(&di->di_tv); + if (retval) { + rv = vim_to_object(&di->di_tv); + } + tv_clear(&di->di_tv); } // Update the value - copy_tv(&tv, &di->di_tv); + tv_copy(&tv, &di->di_tv); // Clear the temporary variable - clear_tv(&tv); + tv_clear(&tv); } return rv; @@ -182,7 +265,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; } @@ -193,9 +276,7 @@ Object get_option_from(void *from, int type, String name, Error *err) type, from); if (!flags) { - api_set_error(err, - Validation, - _("Invalid option name \"%s\""), + api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'", name.data); return rv; } @@ -212,15 +293,14 @@ Object get_option_from(void *from, int type, String name, Error *err) rv.data.string.data = stringval; rv.data.string.size = strlen(stringval); } else { - api_set_error(err, - Exception, - _("Unable to get value for option \"%s\""), + api_set_error(err, kErrorTypeException, + "Failed 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); } @@ -234,35 +314,32 @@ Object get_option_from(void *from, int type, String name, Error *err) /// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF` /// @param name The option name /// @param[out] err Details of an error that may have occurred -void set_option_to(void *to, int type, String name, Object value, Error *err) +void set_option_to(uint64_t channel_id, 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; } int flags = get_option_value_strict(name.data, NULL, NULL, type, to); if (flags == 0) { - api_set_error(err, - Validation, - _("Invalid option name \"%s\""), + api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'", name.data); return; } if (value.type == kObjectTypeNil) { if (type == SREQ_GLOBAL) { - api_set_error(err, - Exception, - _("Unable to unset option \"%s\""), + api_set_error(err, kErrorTypeException, "Cannot 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 { @@ -271,69 +348,74 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) } } - int opt_flags = (type ? OPT_LOCAL : OPT_GLOBAL); + int numval = 0; + char *stringval = NULL; 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; } - bool val = value.data.boolean; - set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); + numval = value.data.boolean; } else if (flags & SOPT_NUM) { if (value.type != kObjectTypeInteger) { - api_set_error(err, - Validation, - _("Option \"%s\" requires an integer value"), + api_set_error(err, 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"), + api_set_error(err, kErrorTypeValidation, + "Value for option '%s' is out of range", name.data); return; } - int val = (int) value.data.integer; - set_option_value_for(name.data, val, NULL, opt_flags, type, to, err); + numval = (int)value.data.integer; } else { if (value.type != kObjectTypeString) { - api_set_error(err, - Validation, - _("Option \"%s\" requires a string value"), + api_set_error(err, kErrorTypeValidation, + "Option '%s' requires a string value", name.data); return; } - set_option_value_for(name.data, 0, value.data.string.data, - opt_flags, type, to, err); + stringval = (char *)value.data.string.data; } + + const scid_T save_current_SID = current_SID; + current_SID = channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_channel_id = channel_id; + + const int opt_flags = (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL; + set_option_value_for(name.data, numval, stringval, + opt_flags, type, to, err); + + current_SID = save_current_SID; } #define TYPVAL_ENCODE_ALLOW_SPECIALS false -#define TYPVAL_ENCODE_CONV_NIL() \ +#define TYPVAL_ENCODE_CONV_NIL(tv) \ kv_push(edata->stack, NIL) -#define TYPVAL_ENCODE_CONV_BOOL(num) \ +#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \ kv_push(edata->stack, BOOLEAN_OBJ((Boolean)(num))) -#define TYPVAL_ENCODE_CONV_NUMBER(num) \ +#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \ kv_push(edata->stack, INTEGER_OBJ((Integer)(num))) #define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER TYPVAL_ENCODE_CONV_NUMBER -#define TYPVAL_ENCODE_CONV_FLOAT(flt) \ - kv_push(edata->stack, FLOATING_OBJ((Float)(flt))) +#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \ + kv_push(edata->stack, FLOAT_OBJ((Float)(flt))) -#define TYPVAL_ENCODE_CONV_STRING(str, len) \ +#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \ do { \ const size_t len_ = (size_t)(len); \ const char *const str_ = (const char *)(str); \ @@ -346,16 +428,23 @@ void set_option_to(void *to, int type, String name, Object value, Error *err) #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING -#define TYPVAL_ENCODE_CONV_EXT_STRING(str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ + TYPVAL_ENCODE_CONV_NIL(tv) + +#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ + do { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + goto typval_encode_stop_converting_one_item; \ + } while (0) -#define TYPVAL_ENCODE_CONV_FUNC(fun) \ - TYPVAL_ENCODE_CONV_NIL() +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, len) +#define TYPVAL_ENCODE_CONV_FUNC_END(tv) -#define TYPVAL_ENCODE_CONV_EMPTY_LIST() \ +#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \ kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 }))) -#define TYPVAL_ENCODE_CONV_EMPTY_DICT() \ +#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ kv_push(edata->stack, \ DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 }))) @@ -363,17 +452,18 @@ static inline void typval_encode_list_start(EncodedData *const edata, const size_t len) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { - const Object obj = OBJECT_INIT; kv_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = len, .size = 0, - .items = xmalloc(len * sizeof(*obj.data.array.items)), + .items = xmalloc(len * sizeof(*((Object)OBJECT_INIT).data.array.items)), }))); } -#define TYPVAL_ENCODE_CONV_LIST_START(len) \ +#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \ typval_encode_list_start(edata, (size_t)(len)) +#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv) + static inline void typval_encode_between_list_items(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { @@ -384,7 +474,7 @@ static inline void typval_encode_between_list_items(EncodedData *const edata) list->data.array.items[list->data.array.size++] = item; } -#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(tv) \ typval_encode_between_list_items(edata) static inline void typval_encode_list_end(EncodedData *const edata) @@ -397,25 +487,27 @@ static inline void typval_encode_list_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_LIST_END() \ +#define TYPVAL_ENCODE_CONV_LIST_END(tv) \ typval_encode_list_end(edata) static inline void typval_encode_dict_start(EncodedData *const edata, const size_t len) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL { - const Object obj = OBJECT_INIT; kv_push(edata->stack, DICTIONARY_OBJ(((Dictionary) { .capacity = len, .size = 0, - .items = xmalloc(len * sizeof(*obj.data.dictionary.items)), + .items = xmalloc(len * sizeof( + *((Object)OBJECT_INIT).data.dictionary.items)), }))); } -#define TYPVAL_ENCODE_CONV_DICT_START(len) \ +#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \ typval_encode_dict_start(edata, (size_t)(len)) -#define TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK(label, kv_pair) +#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv) + +#define TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(label, kv_pair) static inline void typval_encode_after_key(EncodedData *const edata) FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL @@ -434,7 +526,7 @@ static inline void typval_encode_after_key(EncodedData *const edata) } } -#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY() \ +#define TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(tv, dict) \ typval_encode_after_key(edata) static inline void typval_encode_between_dict_items(EncodedData *const edata) @@ -447,7 +539,7 @@ static inline void typval_encode_between_dict_items(EncodedData *const edata) dict->data.dictionary.items[dict->data.dictionary.size++].value = val; } -#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS() \ +#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \ typval_encode_between_dict_items(edata) static inline void typval_encode_dict_end(EncodedData *const edata) @@ -460,31 +552,44 @@ static inline void typval_encode_dict_end(EncodedData *const edata) #endif } -#define TYPVAL_ENCODE_CONV_DICT_END() \ +#define TYPVAL_ENCODE_CONV_DICT_END(tv, dict) \ typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - TYPVAL_ENCODE_CONV_NIL() - -TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) + TYPVAL_ENCODE_CONV_NIL(val) + +#define TYPVAL_ENCODE_SCOPE static +#define TYPVAL_ENCODE_NAME object +#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const +#define TYPVAL_ENCODE_FIRST_ARG_NAME edata +#include "nvim/eval/typval_encode.c.h" +#undef TYPVAL_ENCODE_SCOPE +#undef TYPVAL_ENCODE_NAME +#undef TYPVAL_ENCODE_FIRST_ARG_TYPE +#undef TYPVAL_ENCODE_FIRST_ARG_NAME #undef TYPVAL_ENCODE_CONV_STRING #undef TYPVAL_ENCODE_CONV_STR_STRING #undef TYPVAL_ENCODE_CONV_EXT_STRING #undef TYPVAL_ENCODE_CONV_NUMBER #undef TYPVAL_ENCODE_CONV_FLOAT -#undef TYPVAL_ENCODE_CONV_FUNC +#undef TYPVAL_ENCODE_CONV_FUNC_START +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS +#undef TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF +#undef TYPVAL_ENCODE_CONV_FUNC_END #undef TYPVAL_ENCODE_CONV_EMPTY_LIST #undef TYPVAL_ENCODE_CONV_LIST_START +#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START #undef TYPVAL_ENCODE_CONV_EMPTY_DICT #undef TYPVAL_ENCODE_CONV_NIL #undef TYPVAL_ENCODE_CONV_BOOL #undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER #undef TYPVAL_ENCODE_CONV_DICT_START +#undef TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START #undef TYPVAL_ENCODE_CONV_DICT_END #undef TYPVAL_ENCODE_CONV_DICT_AFTER_KEY #undef TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS -#undef TYPVAL_ENCODE_CONV_SPECIAL_DICT_KEY_CHECK +#undef TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK #undef TYPVAL_ENCODE_CONV_LIST_END #undef TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS #undef TYPVAL_ENCODE_CONV_RECURSE @@ -498,7 +603,10 @@ TYPVAL_ENCODE_DEFINE_CONV_FUNCTIONS(static, object, EncodedData *const, edata) Object vim_to_object(typval_T *obj) { EncodedData edata = { .stack = KV_INITIAL_VALUE }; - encode_vim_to_object(&edata, obj, "vim_to_object argument"); + const int evo_ret = encode_vim_to_object(&edata, obj, + "vim_to_object argument"); + (void)evo_ret; + assert(evo_ret == OK); Object ret = kv_A(edata.stack, 0); assert(kv_size(edata.stack) == 1); kv_destroy(edata.stack); @@ -507,37 +615,65 @@ Object vim_to_object(typval_T *obj) buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { + if (buffer == 0) { + return curbuf; + } + 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; + } + 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; + } + 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. @@ -548,16 +684,33 @@ tabpage_T * find_tab_by_handle(Tabpage tabpage, Error *err) String cstr_to_string(const char *str) { if (str == NULL) { - return (String) STRING_INIT; + return (String)STRING_INIT; } size_t len = strlen(str); - return (String) { - .data = xmemdupz(str, len), - .size = len + return (String){ + .data = xmemdupz(str, len), + .size = len, }; } +/// 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. /// @@ -567,15 +720,58 @@ String cstr_to_string(const char *str) String cstr_as_string(char *str) FUNC_ATTR_PURE { if (str == NULL) { - return (String) STRING_INIT; + return (String)STRING_INIT; } - return (String) {.data = str, .size = strlen(str)}; + return (String){ .data = str, .size = strlen(str) }; } +/// Collects `n` buffer lines into array `l`, optionally replacing newlines +/// with NUL. +/// +/// @param buf Buffer to get lines from +/// @param n Number of lines to collect +/// @param replace_nl Replace newlines ("\n") with NUL +/// @param start Line number to start from +/// @param[out] l Lines are copied here +/// @param err[out] Error, if any +/// @return true unless `err` was set +bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, + Array *l, Error *err) +{ + for (size_t i = 0; i < n; i++) { + int64_t lnum = start + (int64_t)i; + + if (lnum >= MAXLNUM) { + if (err != NULL) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + } + return false; + } + + const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + Object str = STRING_OBJ(cstr_to_string(bufstr)); + + if (replace_nl) { + // Vim represents NULs as NLs, but this may confuse clients. + strchrsub(str.data.string.data, '\n', '\0'); + } + + l->items[i] = str; + } + + return true; +} + +/// Converts from type Object to a VimL value. +/// +/// @param obj Object to convert from. +/// @param tv Conversion result is placed here. On failure member v_type is +/// set to VAR_UNKNOWN (no allocation was made for this variable). +/// returns true if conversion is successful, otherwise false. bool object_to_vim(Object obj, typval_T *tv, Error *err) { tv->v_type = VAR_UNKNOWN; - tv->v_lock = 0; + tv->v_lock = VAR_UNLOCKED; switch (obj.type) { case kObjectTypeNil: @@ -592,13 +788,10 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) case kObjectTypeWindow: case kObjectTypeTabpage: case kObjectTypeInteger: - if (obj.data.integer > INT_MAX || obj.data.integer < INT_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 = (int)obj.data.integer; + tv->vval.v_number = (varnumber_T)obj.data.integer; break; case kObjectTypeFloat: @@ -616,56 +809,59 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) } break; - case kObjectTypeArray: - tv->v_type = VAR_LIST; - tv->vval.v_list = list_alloc(); + case kObjectTypeArray: { + 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 = listitem_alloc(); + typval_T li_tv; - if (!object_to_vim(item, &li->li_tv, err)) { - // cleanup - listitem_free(li); - list_free(tv->vval.v_list, true); + if (!object_to_vim(item, &li_tv, err)) { + tv_list_free(list); return false; } - list_append(tv->vval.v_list, li); + tv_list_append_owned_tv(list, li_tv); } - tv->vval.v_list->lv_refcount++; + tv_list_ref(list); + + tv->v_type = VAR_LIST; + tv->vval.v_list = list; break; + } - case kObjectTypeDictionary: - tv->v_type = VAR_DICT; - tv->vval.v_dict = dict_alloc(); + case kObjectTypeDictionary: { + dict_T *const dict = tv_dict_alloc(); for (uint32_t i = 0; i < obj.data.dictionary.size; i++) { KeyValuePair item = obj.data.dictionary.items[i]; 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 - dict_free(tv->vval.v_dict, true); + tv_dict_free(dict); return false; } - dictitem_T *di = dictitem_alloc((uint8_t *) key.data); + dictitem_T *const di = tv_dict_item_alloc(key.data); if (!object_to_vim(item.value, &di->di_tv, err)) { // cleanup - dictitem_free(di); - dict_free(tv->vval.v_dict, true); + tv_dict_item_free(di); + tv_dict_free(dict); return false; } - dict_add(tv->vval.v_dict, di); + tv_dict_add(dict, di); } - tv->vval.v_dict->dv_refcount++; + dict->dv_refcount++; + + tv->v_type = VAR_DICT; + tv->vval.v_dict = dict; break; + } default: abort(); } @@ -730,12 +926,25 @@ 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; if (!metadata.size) { - msgpack_rpc_init_function_metadata(&metadata); + 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); } @@ -743,6 +952,44 @@ Dictionary api_metadata(void) return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; } +static void init_function_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)funcs_metadata, + sizeof(funcs_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object functions; + msgpack_rpc_to_object(&unpacked.data, &functions); + msgpack_unpacked_destroy(&unpacked); + 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; @@ -758,18 +1005,25 @@ static void init_error_type_metadata(Dictionary *metadata) PUT(*metadata, "error_types", DICTIONARY_OBJ(types)); } + 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)); PUT(types, "Window", DICTIONARY_OBJ(window_metadata)); @@ -778,6 +1032,34 @@ 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; +} + +Dictionary copy_dictionary(Dictionary dict) +{ + Dictionary rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < dict.size; i++) { + KeyValuePair item = dict.items[i]; + PUT(rv, item.key.data, copy_object(item.value)); + } + return rv; +} + /// Creates a deep clone of an object Object copy_object(Object obj) { @@ -789,23 +1071,13 @@ 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; - for (size_t i = 0; i < obj.data.dictionary.size; i++) { - KeyValuePair item = obj.data.dictionary.items[i]; - PUT(rv, item.key.data, copy_object(item.value)); - } - return DICTIONARY_OBJ(rv); + return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); } default: abort(); @@ -822,7 +1094,7 @@ static void set_option_value_for(char *key, { win_T *save_curwin = NULL; tabpage_T *save_curtab = NULL; - buf_T *save_curbuf = NULL; + bufref_T save_curbuf = { NULL, 0, 0 }; try_start(); switch (opt_type) @@ -835,8 +1107,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); @@ -845,14 +1117,14 @@ static void set_option_value_for(char *key, case SREQ_BUF: switch_buffer(&save_curbuf, (buf_T *)from); set_option_value_err(key, numval, stringval, opt_flags, err); - restore_buffer(save_curbuf); + restore_buffer(&save_curbuf); break; case SREQ_GLOBAL: set_option_value_err(key, numval, stringval, opt_flags, err); break; } - if (err->set) { + if (ERROR_SET(err)) { return; } @@ -868,15 +1140,69 @@ static void set_option_value_err(char *key, { char *errmsg; - if ((errmsg = (char *)set_option_value((uint8_t *)key, - numval, - (uint8_t *)stringval, - opt_flags))) - { + if ((errmsg = set_option_value(key, numval, stringval, opt_flags))) { if (try_end(err)) { 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 FUNC_ATTR_PRINTF(3, 4) +{ + 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 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 a946e35149..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 }) @@ -37,15 +29,15 @@ #define BUFFER_OBJ(s) ((Object) { \ .type = kObjectTypeBuffer, \ - .data.buffer = s }) + .data.integer = s }) #define WINDOW_OBJ(s) ((Object) { \ .type = kObjectTypeWindow, \ - .data.window = s }) + .data.integer = s }) #define TABPAGE_OBJ(s) ((Object) { \ .type = kObjectTypeTabpage, \ - .data.tabpage = s }) + .data.integer = s }) #define ARRAY_OBJ(a) ((Object) { \ .type = kObjectTypeArray, \ @@ -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 c8311b0aa0..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> @@ -11,10 +14,11 @@ /// Gets the windows in a tabpage /// -/// @param tabpage The tabpage -/// @param[out] err Details of an error that may have occurred -/// @return The windows in `tabpage` -ArrayOf(Window) tabpage_get_windows(Tabpage tabpage, Error *err) +/// @param tabpage Tabpage +/// @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) { Array rv = ARRAY_DICT_INIT; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -39,11 +43,12 @@ ArrayOf(Window) tabpage_get_windows(Tabpage tabpage, Error *err) /// Gets a tab-scoped (t:) variable /// -/// @param tabpage The tab page handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object tabpage_get_var(Tabpage tabpage, String name, Error *err) +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @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) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -56,11 +61,51 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err) /// Sets a tab-scoped (t:) variable /// -/// @param tabpage handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +void nvim_tabpage_set_var(Tabpage tabpage, + String name, + Object value, + Error *err) + FUNC_API_SINCE(1) +{ + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return; + } + + dict_set_var(tab->tp_vars, name, value, false, false, err); +} + +/// Removes a tab-scoped (t:) variable +/// +/// @param tabpage Tabpage handle +/// @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) +{ + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return; + } + + dict_set_var(tab->tp_vars, name, NIL, true, false, err); +} + +/// Sets a tab-scoped (t:) variable +/// +/// @deprecated +/// +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @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`. @@ -72,18 +117,17 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(tab->tp_vars, name, value, false, err); + return dict_set_var(tab->tp_vars, name, value, false, true, err); } /// Removes a tab-scoped (t:) variable /// -/// @param tabpage handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object tabpage_del_var(Tabpage tabpage, String name, Error *err) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -92,15 +136,16 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(tab->tp_vars, name, NIL, true, err); + return dict_set_var(tab->tp_vars, name, NIL, true, true, err); } -/// Gets the current window in a tab page +/// Gets the current window in a tabpage /// -/// @param tabpage The tab page handle -/// @param[out] err Details of an error that may have occurred -/// @return The Window handle -Window tabpage_get_window(Tabpage tabpage, Error *err) +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +/// @return Window handle +Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) + FUNC_API_SINCE(1) { Window rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -110,7 +155,7 @@ Window tabpage_get_window(Tabpage tabpage, Error *err) } if (tab == curtab) { - return vim_get_current_window(); + return nvim_get_current_win(); } else { FOR_ALL_WINDOWS_IN_TAB(wp, tab) { if (wp == tab->tp_curwin) { @@ -122,13 +167,34 @@ Window tabpage_get_window(Tabpage tabpage, Error *err) } } -/// Checks if a tab page is valid +/// Gets the tabpage number +/// +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +/// @return Tabpage number +Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) + FUNC_API_SINCE(1) +{ + Integer rv = 0; + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return rv; + } + + return tabpage_index(tab); +} + +/// Checks if a tabpage is valid /// -/// @param tabpage The tab page handle -/// @return true if the tab page is valid, false otherwise -Boolean tabpage_is_valid(Tabpage tabpage) +/// @param tabpage Tabpage handle +/// @return true if the tabpage is valid, false otherwise +Boolean nvim_tabpage_is_valid(Tabpage tabpage) + 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 1703d49296..7ba5251c60 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> @@ -8,120 +11,264 @@ #include "nvim/memory.h" #include "nvim/map.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/api/ui.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/popupmnu.h" +#include "nvim/cursor_shape.h" +#include "nvim/highlight.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" +# include "ui_events_remote.generated.h" #endif typedef struct { uint64_t channel_id; Array buffer; + + int hl_id; // current higlight for legacy put event + Integer cursor_row, cursor_col; // Intended visibule cursor position + + // Position of legacy cursor, used both for drawing and visible user cursor. + Integer client_row, client_col; } UIData; 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 ui_attach(uint64_t channel_id, Integer width, Integer height, - Boolean enable_rgb, Error *err) +/// Wait until ui has connected on stdio channel. +void remote_ui_wait_for_attach(void) + FUNC_API_NOEXPORT +{ + Channel *channel = find_channel(CHAN_STDIO); + if (!channel) { + // this function should only be called in --embed mode, stdio channel + // can be assumed. + abort(); + } + + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, + pmap_has(uint64_t)(connected_uis, CHAN_STDIO)); +} + +void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, + Dictionary options, Error *err) + 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; } - UIData *data = xmalloc(sizeof(UIData)); - data->channel_id = channel_id; - data->buffer = (Array)ARRAY_DICT_INIT; UI *ui = xcalloc(1, sizeof(UI)); ui->width = (int)width; ui->height = (int)height; - ui->rgb = enable_rgb; - ui->data = data; - 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->rgb = true; + ui->grid_resize = remote_ui_grid_resize; + ui->grid_clear = remote_ui_grid_clear; + ui->grid_cursor_goto = remote_ui_grid_cursor_goto; + ui->mode_info_set = remote_ui_mode_info_set; ui->update_menu = remote_ui_update_menu; ui->busy_start = remote_ui_busy_start; ui->busy_stop = remote_ui_busy_stop; ui->mouse_on = remote_ui_mouse_on; ui->mouse_off = remote_ui_mouse_off; ui->mode_change = remote_ui_mode_change; - ui->set_scroll_region = remote_ui_set_scroll_region; - ui->scroll = remote_ui_scroll; - ui->highlight_set = remote_ui_highlight_set; - ui->put = remote_ui_put; + ui->grid_scroll = remote_ui_grid_scroll; + ui->hl_attr_define = remote_ui_hl_attr_define; + ui->raw_line = remote_ui_raw_line; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; - ui->update_fg = remote_ui_update_fg; - ui->update_bg = remote_ui_update_bg; - ui->update_sp = remote_ui_update_sp; + ui->default_colors_set = remote_ui_default_colors_set; ui->flush = remote_ui_flush; ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; + ui->option_set = remote_ui_option_set; + ui->event = remote_ui_event; + ui->inspect = remote_ui_inspect; + + memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); + + for (size_t i = 0; i < options.size; i++) { + ui_set_option(ui, true, options.items[i].key, options.items[i].value, err); + if (ERROR_SET(err)) { + xfree(ui); + return; + } + } + + if (ui->ui_ext[kUIHlState] || ui->ui_ext[kUIMultigrid]) { + ui->ui_ext[kUILinegrid] = true; + } + + UIData *data = xmalloc(sizeof(UIData)); + data->channel_id = channel_id; + data->buffer = (Array)ARRAY_DICT_INIT; + data->hl_id = 0; + data->client_col = -1; + ui->data = data; + pmap_put(uint64_t)(connected_uis, channel_id, ui); ui_attach_impl(ui); - return; } -void ui_detach(uint64_t channel_id, Error *err) +/// @deprecated +void ui_attach(uint64_t channel_id, Integer width, Integer height, + Boolean enable_rgb, Error *err) +{ + Dictionary opts = ARRAY_DICT_INIT; + PUT(opts, "rgb", BOOLEAN_OBJ(enable_rgb)); + nvim_ui_attach(channel_id, width, height, opts, err); + api_free_dictionary(opts); +} + +void nvim_ui_detach(uint64_t channel_id, Error *err) + 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); } -Object ui_try_resize(uint64_t channel_id, Integer width, - Integer height, Error *err) + +void nvim_ui_try_resize(uint64_t channel_id, Integer width, + Integer height, Error *err) + 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")); - return NIL; + api_set_error(err, kErrorTypeValidation, + "Expected width > 0 and height > 0"); + return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); ui->width = (int)width; ui->height = (int)height; ui_refresh(); - return NIL; } -static void push_call(UI *ui, char *name, Array args) +void nvim_ui_set_option(uint64_t channel_id, String name, + Object value, Error *error) + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); + + ui_set_option(ui, false, name, value, error); +} + +static void ui_set_option(UI *ui, bool init, String name, Object value, + Error *error) +{ + if (strequal(name.data, "rgb")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); + return; + } + ui->rgb = value.data.boolean; + // A little drastic, but only legacy uis need to use this option + if (!init) { + ui_refresh(); + } + return; + } + + // LEGACY: Deprecated option, use `ext_cmdline` instead. + bool is_popupmenu = strequal(name.data, "popupmenu_external"); + + for (UIExtension i = 0; i < kUIExtCount; i++) { + if (strequal(name.data, ui_ext_names[i]) + || (i == kUIPopupmenu && is_popupmenu)) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "%s must be a Boolean", + name.data); + return; + } + bool boolval = value.data.boolean; + if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) { + // There shouldn't be a reason for an UI to do this ever + // so explicitly don't support this. + api_set_error(error, kErrorTypeValidation, + "ext_linegrid option cannot be changed"); + } + ui->ui_ext[i] = boolval; + if (!init) { + ui_set_ext_option(ui, i, boolval); + } + return; + } + } + + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); +} + +/// Tell nvim to resize a grid. Nvim sends grid_resize event with the +/// requested grid size is within size limits and with maximum allowed size +/// otherwise. +/// +/// On invalid grid handle, fails with error. +/// +/// @param grid The handle of the grid to be changed. +/// @param width The new requested width. +/// @param height The new requested height. +void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, + Integer height, Error *error) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(connected_uis, channel_id)) { + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + + ui_grid_resize((handle_T)grid, (int)width, (int)height, error); +} + +/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush(). +static void push_call(UI *ui, const char *name, Array args) { Array call = ARRAY_DICT_INIT; UIData *data = ui->data; @@ -143,201 +290,329 @@ 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) +static void remote_ui_grid_clear(UI *ui, Integer grid) { 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); + if (ui->ui_ext[kUILinegrid]) { + ADD(args, INTEGER_OBJ(grid)); + } + const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear"; + push_call(ui, name, args); } -static void remote_ui_update_menu(UI *ui) +static void remote_ui_grid_resize(UI *ui, Integer grid, + Integer width, Integer height) { Array args = ARRAY_DICT_INIT; - push_call(ui, "update_menu", args); + if (ui->ui_ext[kUILinegrid]) { + ADD(args, INTEGER_OBJ(grid)); + } + ADD(args, INTEGER_OBJ(width)); + ADD(args, INTEGER_OBJ(height)); + const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize"; + push_call(ui, name, args); } -static void remote_ui_busy_start(UI *ui) +static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, + Integer bot, Integer left, Integer right, + Integer rows, Integer cols) { - Array args = ARRAY_DICT_INIT; - push_call(ui, "busy_start", args); + if (ui->ui_ext[kUILinegrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right)); + ADD(args, INTEGER_OBJ(rows)); + ADD(args, INTEGER_OBJ(cols)); + push_call(ui, "grid_scroll", args); + } else { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(top)); + ADD(args, INTEGER_OBJ(bot-1)); + ADD(args, INTEGER_OBJ(left)); + ADD(args, INTEGER_OBJ(right-1)); + push_call(ui, "set_scroll_region", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(rows)); + push_call(ui, "scroll", args); + + // some clients have "clear" being affected by scroll region, + // so reset it. + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(0)); + ADD(args, INTEGER_OBJ(ui->height-1)); + ADD(args, INTEGER_OBJ(0)); + ADD(args, INTEGER_OBJ(ui->width-1)); + push_call(ui, "set_scroll_region", args); + } } -static void remote_ui_busy_stop(UI *ui) +static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, + Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { Array args = ARRAY_DICT_INIT; - push_call(ui, "busy_stop", args); + ADD(args, INTEGER_OBJ(rgb_fg)); + ADD(args, INTEGER_OBJ(rgb_bg)); + ADD(args, INTEGER_OBJ(rgb_sp)); + ADD(args, INTEGER_OBJ(cterm_fg)); + ADD(args, INTEGER_OBJ(cterm_bg)); + push_call(ui, "default_colors_set", args); + + // Deprecated + if (!ui->ui_ext[kUILinegrid]) { + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1)); + push_call(ui, "update_fg", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_bg : cterm_bg - 1)); + push_call(ui, "update_bg", args); + + args = (Array)ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(ui->rgb ? rgb_sp : -1)); + push_call(ui, "update_sp", args); + } } -static void remote_ui_mouse_on(UI *ui) +static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, + HlAttrs cterm_attrs, Array info) { + if (!ui->ui_ext[kUILinegrid]) { + return; + } 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); -} + ADD(args, INTEGER_OBJ(id)); + ADD(args, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs, true))); + ADD(args, DICTIONARY_OBJ(hlattrs2dict(cterm_attrs, false))); -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"))); + if (ui->ui_ext[kUIHlState]) { + ADD(args, ARRAY_OBJ(copy_array(info))); } else { - assert(mode == NORMAL); - ADD(args, STRING_OBJ(cstr_to_string("normal"))); + ADD(args, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); } - 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); + push_call(ui, "hl_attr_define", args); } -static void remote_ui_scroll(UI *ui, int count) +static void remote_ui_highlight_set(UI *ui, int id) { Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(count)); - push_call(ui, "scroll", 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)); - } + UIData *data = ui->data; - if (attrs.background != -1) { - PUT(hl, "background", INTEGER_OBJ(attrs.background)); - } - if (attrs.special != -1) { - PUT(hl, "special", INTEGER_OBJ(attrs.special)); + if (data->hl_id == id) { + return; } + data->hl_id = id; + Dictionary hl = hlattrs2dict(syn_attr2entry(id), 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) +/// "true" cursor used only for input focus +static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, + Integer col) { - Array args = ARRAY_DICT_INIT; - String str = { .data = xmemdupz(data, size), .size = size }; - ADD(args, STRING_OBJ(str)); - push_call(ui, "put", args); + if (ui->ui_ext[kUILinegrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "grid_cursor_goto", args); + } else { + UIData *data = ui->data; + data->cursor_row = row; + data->cursor_col = col; + remote_ui_cursor_goto(ui, row, col); + } } -static void remote_ui_bell(UI *ui) +/// emulated cursor used both for drawing and for input focus +static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) { + UIData *data = ui->data; + if (data->client_row == row && data->client_col == col) { + return; + } + data->client_row = row; + data->client_col = col; Array args = ARRAY_DICT_INIT; - push_call(ui, "bell", args); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(col)); + push_call(ui, "cursor_goto", args); } -static void remote_ui_visual_bell(UI *ui) +static void remote_ui_put(UI *ui, const char *cell) { + UIData *data = ui->data; + data->client_col++; Array args = ARRAY_DICT_INIT; - push_call(ui, "visual_bell", args); + ADD(args, STRING_OBJ(cstr_to_string(cell))); + push_call(ui, "put", args); } -static void remote_ui_update_fg(UI *ui, int fg) +static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, + Boolean wrap, const schar_T *chunk, + const sattr_T *attrs) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(fg)); - push_call(ui, "update_fg", args); + UIData *data = ui->data; + if (ui->ui_ext[kUILinegrid]) { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(grid)); + ADD(args, INTEGER_OBJ(row)); + ADD(args, INTEGER_OBJ(startcol)); + Array cells = ARRAY_DICT_INIT; + int repeat = 0; + size_t ncells = (size_t)(endcol-startcol); + int last_hl = -1; + for (size_t i = 0; i < ncells; i++) { + repeat++; + if (i == ncells-1 || attrs[i] != attrs[i+1] + || STRCMP(chunk[i], chunk[i+1])) { + Array cell = ARRAY_DICT_INIT; + ADD(cell, STRING_OBJ(cstr_to_string((const char *)chunk[i]))); + if (attrs[i] != last_hl || repeat > 1) { + ADD(cell, INTEGER_OBJ(attrs[i])); + last_hl = attrs[i]; + } + if (repeat > 1) { + ADD(cell, INTEGER_OBJ(repeat)); + } + ADD(cells, ARRAY_OBJ(cell)); + repeat = 0; + } + } + if (endcol < clearcol) { + Array cell = ARRAY_DICT_INIT; + ADD(cell, STRING_OBJ(cstr_to_string(" "))); + ADD(cell, INTEGER_OBJ(clearattr)); + ADD(cell, INTEGER_OBJ(clearcol-endcol)); + ADD(cells, ARRAY_OBJ(cell)); + } + ADD(args, ARRAY_OBJ(cells)); + + push_call(ui, "grid_line", args); + } else { + for (int i = 0; i < endcol-startcol; i++) { + remote_ui_cursor_goto(ui, row, startcol+i); + remote_ui_highlight_set(ui, attrs[i]); + remote_ui_put(ui, (const char *)chunk[i]); + if (utf_ambiguous_width(utf_ptr2char(chunk[i]))) { + data->client_col = -1; // force cursor update + } + } + if (endcol < clearcol) { + remote_ui_cursor_goto(ui, row, endcol); + remote_ui_highlight_set(ui, (int)clearattr); + // legacy eol_clear was only ever used with cleared attributes + // so be on the safe side + if (clearattr == 0 && clearcol == Columns) { + Array args = ARRAY_DICT_INIT; + push_call(ui, "eol_clear", args); + } else { + for (Integer c = endcol; c < clearcol; c++) { + remote_ui_put(ui, " "); + } + } + } + } } -static void remote_ui_update_bg(UI *ui, int bg) +static void remote_ui_flush(UI *ui) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(bg)); - push_call(ui, "update_bg", args); + UIData *data = ui->data; + if (data->buffer.size > 0) { + if (!ui->ui_ext[kUILinegrid]) { + remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col); + } + push_call(ui, "flush", (Array)ARRAY_DICT_INIT); + rpc_send_event(data->channel_id, "redraw", data->buffer); + data->buffer = (Array)ARRAY_DICT_INIT; + } } -static void remote_ui_update_sp(UI *ui, int sp) +static Array translate_contents(UI *ui, Array contents) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(sp)); - push_call(ui, "update_sp", args); + Array new_contents = ARRAY_DICT_INIT; + for (size_t i = 0; i < contents.size; i++) { + Array item = contents.items[i].data.array; + Array new_item = ARRAY_DICT_INIT; + int attr = (int)item.items[0].data.integer; + if (attr) { + Dictionary rgb_attrs = hlattrs2dict(syn_attr2entry(attr), ui->rgb); + ADD(new_item, DICTIONARY_OBJ(rgb_attrs)); + } else { + ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + } + ADD(new_item, copy_object(item.items[1])); + ADD(new_contents, ARRAY_OBJ(new_item)); + } + return new_contents; } -static void remote_ui_flush(UI *ui) +static Array translate_firstarg(UI *ui, Array args) { - UIData *data = ui->data; - channel_send_event(data->channel_id, "redraw", data->buffer); - data->buffer = (Array)ARRAY_DICT_INIT; -} + Array new_args = ARRAY_DICT_INIT; + Array contents = args.items[0].data.array; -static void remote_ui_suspend(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "suspend", args); + ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents))); + for (size_t i = 1; i < args.size; i++) { + ADD(new_args, copy_object(args.items[i])); + } + return new_args; } -static void remote_ui_set_title(UI *ui, char *title) +static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(title))); - push_call(ui, "set_title", args); + if (!ui->ui_ext[kUILinegrid]) { + // the representation of highlights in cmdline changed, translate back + // never consumes args + if (strequal(name, "cmdline_show")) { + Array new_args = translate_firstarg(ui, args); + push_call(ui, name, new_args); + return; + } else if (strequal(name, "cmdline_block_show")) { + Array new_args = ARRAY_DICT_INIT; + Array block = args.items[0].data.array; + Array new_block = ARRAY_DICT_INIT; + for (size_t i = 0; i < block.size; i++) { + ADD(new_block, + ARRAY_OBJ(translate_contents(ui, block.items[i].data.array))); + } + ADD(new_args, ARRAY_OBJ(new_block)); + push_call(ui, name, new_args); + return; + } else if (strequal(name, "cmdline_block_append")) { + Array new_args = translate_firstarg(ui, args); + push_call(ui, name, new_args); + return; + } + } + + Array my_args = ARRAY_DICT_INIT; + // Objects are currently single-reference + // make a copy, but only if necessary + if (*args_consumed) { + for (size_t i = 0; i < args.size; i++) { + ADD(my_args, copy_object(args.items[i])); + } + } else { + my_args = args; + *args_consumed = true; + } + push_call(ui, name, my_args); } -static void remote_ui_set_icon(UI *ui, char *icon) +static void remote_ui_inspect(UI *ui, Dictionary *info) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(icon))); - push_call(ui, "set_icon", args); + UIData *data = ui->data; + PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id)); } diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h new file mode 100644 index 0000000000..59a7780651 --- /dev/null +++ b/src/nvim/api/ui_events.in.h @@ -0,0 +1,130 @@ +#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 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 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 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) FUNC_API_BRIDGE_IMPL; + +// First revison of the grid protocol, used by default +void update_fg(Integer fg) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void update_bg(Integer bg) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void update_sp(Integer sp) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void resize(Integer width, Integer height) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void clear(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void eol_clear(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void cursor_goto(Integer row, Integer col) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void highlight_set(HlAttrs attrs) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY FUNC_API_REMOTE_IMPL; +void put(String str) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void scroll(Integer count) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +// Second revison of the grid protocol, used with ext_linegrid ui option +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) + FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, + Array info) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void grid_resize(Integer grid, Integer width, Integer height) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_clear(Integer grid) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_cursor_goto(Integer grid, Integer row, Integer col) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_line(Integer grid, Integer row, Integer col_start, Array data) + FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; +void grid_scroll(Integer grid, Integer top, Integer bot, + Integer left, Integer right, Integer rows, Integer cols) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; +void grid_destroy(Integer grid) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + +void win_pos(Integer grid, Integer win, Integer startrow, + Integer startcol, Integer width, Integer height) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void win_hide(Integer grid) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void win_scroll_over_start(void) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +void win_scroll_over_reset(void) + FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + +void popupmenu_show(Array items, Integer selected, + Integer row, Integer col, Integer grid) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void popupmenu_hide(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void popupmenu_select(Integer selected) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +void tabline_update(Tabpage current, Array tabs) + 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 ac7cc65ee4..ce7ef681ef 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,21 +13,33 @@ #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/msgpack_rpc/helpers.h" +#include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/file_search.h" +#include "nvim/highlight.h" #include "nvim/window.h" #include "nvim/types.h" #include "nvim/ex_docmd.h" #include "nvim/screen.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/misc2.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 @@ -32,36 +47,98 @@ # include "api/vim.c.generated.h" #endif -/// Executes an ex-mode command str +void api_vim_init(void) + FUNC_API_NOEXPORT +{ + namespace_ids = map_new(String, handle_T)(); +} + +void api_vim_free_all_mem(void) + FUNC_API_NOEXPORT +{ + String name; + handle_T id; + map_foreach(namespace_ids, name, id, { + (void)id; + xfree(name.data); + }) + map_free(String, handle_T)(namespace_ids); +} + +/// Executes an ex-command. /// -/// @param str The command str -/// @param[out] err Details of an error that may have occurred -void vim_command(String str, Error *err) +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +void nvim_command(String command, Error *err) + FUNC_API_SINCE(1) { - // Run the command try_start(); - do_cmdline_cmd(str.data); - update_screen(VALID); + do_cmdline_cmd(command.data); try_end(err); } -/// Passes input keys to Neovim +/// Gets a highlight definition by name. /// -/// @param keys to be typed -/// @param mode specifies the mapping options -/// @param escape_csi the string needs escaping for K_SPECIAL/CSI bytes +/// @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); +} + +/// Sends input-keys to Nvim, subject to various quirks controlled by `mode` +/// flags. This is a blocking call, unlike |nvim_input()|. +/// +/// On execution error: does not fail, but updates v:errmsg. +/// +/// @param keys to be typed +/// @param mode behavior flags, see |feedkeys()| +/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` /// @see feedkeys() /// @see vim_strsave_escape_csi -void vim_feedkeys(String keys, String mode, Boolean escape_csi) +void nvim_feedkeys(String keys, String mode, Boolean escape_csi) + FUNC_API_SINCE(1) { bool remap = true; bool insert = false; bool typed = false; bool execute = false; - - if (keys.size == 0) { - return; - } + bool dangerous = false; for (size_t i = 0; i < mode.size; ++i) { switch (mode.data[i]) { @@ -70,9 +147,14 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi) case 't': typed = true; break; case 'i': insert = true; break; case 'x': execute = true; break; + case '!': dangerous = true; break; } } + if (keys.size == 0 && !execute) { + return; + } + char *keys_esc; if (escape_csi) { // Need to escape K_SPECIAL and CSI before putting the string in the @@ -92,100 +174,193 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi) typebuf_was_filled = true; } if (execute) { + int save_msg_scroll = msg_scroll; + + /* Avoid a 1 second delay when the keys start Insert mode. */ + msg_scroll = false; + if (!dangerous) { + ex_normal_busy++; + } exec_normal(true); + if (!dangerous) { + ex_normal_busy--; + } + msg_scroll |= save_msg_scroll; } } -/// Passes input keys to Neovim. Unlike `vim_feedkeys`, this will use a -/// lower-level input buffer and the call is not deferred. -/// This is the most reliable way to emulate real user input. +/// Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a low-level +/// input buffer and the call is non-blocking (input is processed +/// asynchronously by the eventloop). +/// +/// On execution error: does not fail, but updates v:errmsg. +/// +/// @note |keycodes| like <CR> are translated, so "<" is special. +/// To input a literal "<", send <LT>. /// /// @param keys to be typed -/// @return The number of bytes actually written, which can be lower than -/// requested if the buffer becomes full. -Integer vim_input(String keys) - FUNC_API_ASYNC +/// @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 { 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 vim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, +String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special) + 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 vim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On execution error: fails with VimL 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"); - vim_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; + } + + 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. } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } -/// Evaluates the expression str using the Vim internal expression -/// evaluator (see |expression|). -/// Dictionaries and lists are recursively expanded. +/// Evaluates a VimL expression (:help expression). +/// Dictionaries and Lists are recursively expanded. /// -/// @param str The expression str -/// @param[out] err Details of an error that may have occurred -/// @return The expanded object -Object vim_eval(String str, Error *err) +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param expr VimL expression string +/// @param[out] err Error details, if any +/// @return Evaluation result or expanded object +Object nvim_eval(String expr, Error *err) + FUNC_API_SINCE(1) { + static int recursive = 0; // recursion depth Object rv = OBJECT_INIT; - // Evaluate the expression - try_start(); - typval_T *expr_result = eval_expr((char_u *) str.data, NULL); - if (!expr_result) { - api_set_error(err, Exception, _("Failed to evaluate expression")); + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; } + recursive++; + try_start(); + + typval_T rettv; + int ok = eval0((char_u *)expr.data, &rettv, NULL, true); if (!try_end(err)) { - // No errors, convert the result - rv = vim_to_object(expr_result); + if (ok == FAIL) { + // Should never happen, try_end() should get the error. #8371 + api_set_error(err, kErrorTypeException, "Failed to evaluate expression"); + } else { + rv = vim_to_object(&rettv); + } } - // Free the vim object - free_tv(expr_result); + tv_clear(&rettv); + msg_list = saved_msg_list; // Restore the exception context. + recursive--; + return rv; } -/// Call the given function with the given arguments stored in an array. +/// 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 fname Function to call -/// @param args Functions arguments packed in an Array -/// @param[out] err Details of an error that may have occurred +/// @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); +} + +/// Calls a VimL function. +/// +/// @param fn Function name +/// @param args Function arguments +/// @param self `self` dict, or NULL for non-dict functions +/// @param[out] err Error details, if any /// @return Result of the function call -Object vim_call_function(String fname, Array args, Error *err) +static Object _call_function(String fn, Array args, dict_T *self, Error *err) { + static int recursive = 0; // recursion depth 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; } @@ -198,54 +373,168 @@ Object vim_call_function(String fname, Array args, Error *err) } } + // `msg_list` controls the collection of abort-causing non-exception errors, + // which would otherwise be ignored. This pattern is from do_cmdline(). + struct msglist **saved_msg_list = msg_list; + struct msglist *private_msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + // Initialize `force_abort` and `suppress_errthrow` at the top level. + if (!recursive) { + force_abort = false; + suppress_errthrow = false; + current_exception = NULL; + // `did_emsg` is set by emsg(), which cancels execution. + did_emsg = false; + } + recursive++; try_start(); - // Call the function typval_T rettv; int dummy; - int r = call_func((char_u *) fname.data, (int) fname.size, - &rettv, (int) args.size, vim_args, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy, - true, - NULL); - if (r == FAIL) { - api_set_error(err, Exception, _("Error calling function.")); - } + // call_func() retval is deceptive, ignore it. Instead we set `msg_list` + // (see above) to capture abort-causing non-exception errors. + (void)call_func((char_u *)fn.data, (int)fn.size, &rettv, (int)args.size, + vim_args, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, NULL, self); if (!try_end(err)) { rv = vim_to_object(&rettv); } - clear_tv(&rettv); + tv_clear(&rettv); + msg_list = saved_msg_list; // Restore the exception context. + recursive--; free_vim_args: while (i > 0) { - clear_tv(&vim_args[--i]); + tv_clear(&vim_args[--i]); + } + + return rv; +} + +/// Calls a VimL function with the given arguments. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param fn Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_function(String fn, Array args, Error *err) + FUNC_API_SINCE(1) +{ + return _call_function(fn, args, NULL, err); +} + +/// Calls a VimL |Dictionary-function| with the given arguments. +/// +/// On execution error: fails with VimL error, does not update v:errmsg. +/// +/// @param dict Dictionary, or String evaluating to a VimL |self| dict +/// @param fn Name of the function defined on the VimL dict +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any +/// @return Result of the function call +Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) + FUNC_API_SINCE(4) +{ + Object rv = OBJECT_INIT; + + typval_T rettv; + bool mustfree = false; + switch (dict.type) { + case kObjectTypeString: { + try_start(); + if (eval0((char_u *)dict.data.string.data, &rettv, NULL, true) == FAIL) { + api_set_error(err, kErrorTypeException, + "Failed to evaluate dict expression"); + } + if (try_end(err)) { + return rv; + } + // Evaluation of the string arg created a new dict or increased the + // refcount of a dict. Not necessary for a RPC dict. + mustfree = true; + break; + } + case kObjectTypeDictionary: { + if (!object_to_vim(dict, &rettv, err)) { + goto end; + } + break; + } + default: { + api_set_error(err, kErrorTypeValidation, + "dict argument type must be String or Dictionary"); + return rv; + } + } + dict_T *self_dict = rettv.vval.v_dict; + if (rettv.v_type != VAR_DICT || !self_dict) { + api_set_error(err, kErrorTypeValidation, "dict not found"); + goto end; + } + + if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) { + dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size); + if (di == NULL) { + api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data); + goto end; + } + if (di->di_tv.v_type == VAR_PARTIAL) { + api_set_error(err, kErrorTypeValidation, + "partial function not supported"); + goto end; + } + if (di->di_tv.v_type != VAR_FUNC) { + api_set_error(err, kErrorTypeValidation, "Not a function: %s", fn.data); + goto end; + } + fn = (String) { + .data = (char *)di->di_tv.vval.v_string, + .size = strlen((char *)di->di_tv.vval.v_string), + }; + } + + if (!fn.data || fn.size < 1) { + api_set_error(err, kErrorTypeValidation, "Invalid (empty) function name"); + goto end; + } + + rv = _call_function(fn, args, self_dict, err); +end: + if (mustfree) { + tv_clear(&rettv); } return rv; } -/// Calculates the number of display cells `str` occupies, tab is counted as -/// one cell. +/// Calculates the number of display cells occupied by `text`. +/// <Tab> counts as one cell. /// -/// @param str Some text -/// @param[out] err Details of an error that may have occurred -/// @return The number of cells -Integer vim_strwidth(String str, Error *err) +/// @param text Some text +/// @param[out] err Error details, if any +/// @return Number of cells +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 is too long"); return 0; } - return (Integer) mb_string2cells((char_u *) str.data); + return (Integer)mb_string2cells((char_u *)text.data); } -/// Gets a list of paths contained in 'runtimepath' +/// Gets the paths contained in 'runtimepath'. /// -/// @return The list of paths -ArrayOf(String) vim_list_runtime_paths(void) +/// @return List of paths +ArrayOf(String) nvim_list_runtime_paths(void) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; - uint8_t *rtp = p_rtp; + char_u *rtp = p_rtp; if (*rtp == NUL) { // No paths @@ -259,13 +548,14 @@ ArrayOf(String) vim_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] @@ -279,26 +569,27 @@ ArrayOf(String) vim_list_runtime_paths(void) return rv; } -/// Changes Vim working directory +/// Changes the global working directory. /// -/// @param dir The new working directory -/// @param[out] err Details of an error that may have occurred -void vim_change_directory(String dir, Error *err) +/// @param dir Directory path +/// @param[out] err Error details, if any +void nvim_set_current_dir(String dir, Error *err) + FUNC_API_SINCE(1) { if (dir.size >= MAXPATHL) { - api_set_error(err, Validation, _("Directory string is too long")); + api_set_error(err, kErrorTypeValidation, "Directory name is too long"); return; } char string[MAXPATHL]; - strncpy(string, dir.data, dir.size); + memcpy(string, dir.data, dir.size); string[dir.size] = NUL; try_start(); - if (vim_chdir((char_u *)string)) { + 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; } @@ -309,127 +600,166 @@ void vim_change_directory(String dir, Error *err) /// Gets the current line /// -/// @param[out] err Details of an error that may have occurred -/// @return The current line string -String vim_get_current_line(Error *err) +/// @param[out] err Error details, if any +/// @return Current line string +String nvim_get_current_line(Error *err) + FUNC_API_SINCE(1) { return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } /// Sets the current line /// -/// @param line The line contents -/// @param[out] err Details of an error that may have occurred -void vim_set_current_line(String 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) { buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); } /// Deletes the current line /// -/// @param[out] err Details of an error that may have occurred -void vim_del_current_line(Error *err) +/// @param[out] err Error details, if any +void nvim_del_current_line(Error *err) + FUNC_API_SINCE(1) { buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } -/// Gets a global variable +/// Gets a global (g:) variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object vim_get_var(String name, Error *err) +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_get_var(String name, Error *err) + FUNC_API_SINCE(1) { return dict_get_value(&globvardict, name, err); } -/// Sets a global variable +/// Sets a global (g:) variable /// -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param name Variable name +/// @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) +{ + dict_set_var(&globvardict, name, value, false, false, err); +} + +/// Removes a global (g:) variable /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param name Variable name +/// @param[out] err Error details, if any +void nvim_del_var(String name, Error *err) + FUNC_API_SINCE(1) +{ + dict_set_var(&globvardict, name, NIL, true, false, err); +} + +/// @deprecated +/// @see nvim_set_var +/// @return Old value or nil if there was no previous value. +/// @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_value(&globvardict, name, value, false, err); + return dict_set_var(&globvardict, name, value, false, true, err); } -/// Removes a global variable -/// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The 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`. +/// @deprecated +/// @see nvim_del_var Object vim_del_var(String name, Error *err) { - return dict_set_value(&globvardict, name, NIL, true, err); + return dict_set_var(&globvardict, name, NIL, true, true, err); } -/// Gets a vim variable +/// Gets a v: variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object vim_get_vvar(String name, Error *err) +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value +Object nvim_get_vvar(String name, Error *err) + FUNC_API_SINCE(1) { return dict_get_value(&vimvardict, name, err); } +/// Sets a v: variable, if it is not readonly +/// +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +void nvim_set_vvar(String name, Object value, Error *err) + FUNC_API_SINCE(6) +{ + dict_set_var(&vimvardict, name, value, false, false, err); +} + /// Gets an option value string /// -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value -Object vim_get_option(String name, Error *err) +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value (global) +Object nvim_get_option(String name, Error *err) + FUNC_API_SINCE(1) { return get_option_from(NULL, SREQ_GLOBAL, name, err); } /// Sets an option value /// -/// @param name The option name -/// @param value The new option value -/// @param[out] err Details of an error that may have occurred -void vim_set_option(String name, Object value, Error *err) +/// @param name Option name +/// @param value New option value +/// @param[out] err Error details, if any +void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) + FUNC_API_SINCE(1) { - set_option_to(NULL, SREQ_GLOBAL, name, value, err); + set_option_to(channel_id, 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 The message -void vim_out_write(String str) +/// @param str Message +void nvim_out_write(String str) + 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 The message -void vim_err_write(String str) +/// @param str Message +void nvim_err_write(String str) + FUNC_API_SINCE(1) { write_msg(str, true); } -/// Higher level error reporting function that ensures all str contents -/// are written by sending a trailing linefeed to `vim_err_write` +/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is +/// flushed (and displayed). /// -/// @param str The message -void vim_report_error(String str) +/// @param str Message +/// @see nvim_err_write() +void nvim_err_writeln(String str) + FUNC_API_SINCE(1) { - vim_err_write(str); - vim_err_write((String) {.data = "\n", .size = 1}); + nvim_err_write(str); + nvim_err_write((String) { .data = "\n", .size = 1 }); } /// Gets the current list of buffer handles /// -/// @return The number of buffers -ArrayOf(Buffer) vim_get_buffers(void) +/// Includes unlisted (unloaded/deleted) buffers, like `:ls!`. +/// Use |nvim_buf_is_loaded()| to check if a buffer is loaded. +/// +/// @return List of buffer handles +ArrayOf(Buffer) nvim_list_bufs(void) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; @@ -449,17 +779,19 @@ ArrayOf(Buffer) vim_get_buffers(void) /// Gets the current buffer /// -/// @reqturn The buffer handle -Buffer vim_get_current_buffer(void) +/// @return Buffer handle +Buffer nvim_get_current_buf(void) + FUNC_API_SINCE(1) { return curbuf->handle; } /// Sets the current buffer /// -/// @param id The buffer handle -/// @param[out] err Details of an error that may have occurred -void vim_set_current_buffer(Buffer buffer, Error *err) +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +void nvim_set_current_buf(Buffer buffer, Error *err) + FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -471,16 +803,17 @@ void vim_set_current_buffer(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 %" PRIu64), + kErrorTypeException, + "Failed to switch to buffer %d", buffer); } } /// Gets the current list of window handles /// -/// @return The number of windows -ArrayOf(Window) vim_get_windows(void) +/// @return List of window handles +ArrayOf(Window) nvim_list_wins(void) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; @@ -500,16 +833,18 @@ ArrayOf(Window) vim_get_windows(void) /// Gets the current window /// -/// @return The window handle -Window vim_get_current_window(void) +/// @return Window handle +Window nvim_get_current_win(void) + FUNC_API_SINCE(1) { return curwin->handle; } /// Sets the current window /// -/// @param handle The window handle -void vim_set_current_window(Window window, Error *err) +/// @param window Window handle +void nvim_set_current_win(Window window, Error *err) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -521,16 +856,17 @@ void vim_set_current_window(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 %" PRIu64), + kErrorTypeException, + "Failed to switch to window %d", window); } } /// Gets the current list of tabpage handles /// -/// @return The number of tab pages -ArrayOf(Tabpage) vim_get_tabpages(void) +/// @return List of tabpage handles +ArrayOf(Tabpage) nvim_list_tabpages(void) + FUNC_API_SINCE(1) { Array rv = ARRAY_DICT_INIT; @@ -548,19 +884,21 @@ ArrayOf(Tabpage) vim_get_tabpages(void) return rv; } -/// Gets the current tab page +/// Gets the current tabpage /// -/// @return The tab page handle -Tabpage vim_get_current_tabpage(void) +/// @return Tabpage handle +Tabpage nvim_get_current_tabpage(void) + FUNC_API_SINCE(1) { return curtab->handle; } -/// Sets the current tab page +/// Sets the current tabpage /// -/// @param handle The tab page handle -/// @param[out] err Details of an error that may have occurred -void vim_set_current_tabpage(Tabpage tabpage, Error *err) +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +void nvim_set_current_tabpage(Tabpage tabpage, Error *err) + FUNC_API_SINCE(1) { tabpage_T *tp = find_tab_by_handle(tabpage, err); @@ -572,30 +910,75 @@ void vim_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 %" PRIu64), + kErrorTypeException, + "Failed to switch to tabpage %d", tabpage); } } +/// Creates a new namespace, or gets an existing one +/// +/// Namespaces are used for buffer highlights and virtual text, see +/// |nvim_buf_add_highlight()| and |nvim_buf_set_virtual_text()|. +/// +/// Namespaces can be named or anonymous. If `name` matches an existing +/// namespace, the associated id is returned. If `name` is an empty string +/// a new, anonymous namespace is created. +/// +/// @param name Namespace name or empty string +/// @return Namespace id +Integer nvim_create_namespace(String name) + FUNC_API_SINCE(5) +{ + handle_T id = map_get(String, handle_T)(namespace_ids, name); + if (id > 0) { + return id; + } + id = next_namespace_id++; + if (name.size > 0) { + String name_alloc = copy_string(name); + map_put(String, handle_T)(namespace_ids, name_alloc, id); + } + return (Integer)id; +} + +/// Gets existing, non-anonymous namespaces +/// +/// @return dict that maps from names to namespace ids. +Dictionary nvim_get_namespaces(void) + FUNC_API_SINCE(5) +{ + Dictionary retval = ARRAY_DICT_INIT; + String name; + handle_T id; + + map_foreach(namespace_ids, name, id, { + PUT(retval, name.data, INTEGER_OBJ(id)); + }) + + return retval; +} + /// Subscribes to event broadcasts /// -/// @param channel_id The channel id (passed automatically by the dispatcher) -/// @param event The event type string -void vim_subscribe(uint64_t channel_id, String event) +/// @param channel_id Channel id (passed automatically by the dispatcher) +/// @param event Event type string +void nvim_subscribe(uint64_t channel_id, String event) + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN); char e[METHOD_MAXLEN + 1]; memcpy(e, event.data, length); e[length] = NUL; - channel_subscribe(channel_id, e); + rpc_subscribe(channel_id, e); } /// Unsubscribes to event broadcasts /// -/// @param channel_id The channel id (passed automatically by the dispatcher) -/// @param event The event type string -void vim_unsubscribe(uint64_t channel_id, String event) +/// @param channel_id Channel id (passed automatically by the dispatcher) +/// @param event Event type string +void nvim_unsubscribe(uint64_t channel_id, String event) + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { size_t length = (event.size < METHOD_MAXLEN ? event.size : @@ -603,15 +986,17 @@ void vim_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 vim_name_to_color(String name) +Integer nvim_get_color_by_name(String name) + FUNC_API_SINCE(1) { - return name_to_color((uint8_t *)name.data); + return name_to_color((char_u *)name.data); } -Dictionary vim_get_color_map(void) +Dictionary nvim_get_color_map(void) + FUNC_API_SINCE(1) { Dictionary colors = ARRAY_DICT_INIT; @@ -623,8 +1008,55 @@ Dictionary vim_get_color_map(void) } -Array vim_get_api_info(uint64_t channel_id) - FUNC_API_ASYNC +/// 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 global (non-buffer-local) |mapping| definitions. +/// +/// @param mode Mode short-name ("n", "i", "v", ...) +/// @returns Array of maparg()-like dictionaries describing mappings. +/// The "buffer" key is always zero. +ArrayOf(Dictionary) nvim_get_keymap(String mode) + FUNC_API_SINCE(3) +{ + return keymap_array(mode, NULL); +} + +/// Gets a map of global (non-buffer-local) Ex commands. +/// +/// Currently only |user-commands| are supported, not builtin Ex commands. +/// +/// @param opts Optional parameters. Currently only supports +/// {"builtin":false} +/// @param[out] err Error details, if any. +/// +/// @returns Map of maps describing commands. +Dictionary nvim_get_commands(Dictionary opts, Error *err) + FUNC_API_SINCE(4) +{ + return nvim_buf_get_commands(-1, opts, err); +} + +/// 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_REMOTE_ONLY { Array rv = ARRAY_DICT_INIT; @@ -635,13 +1067,674 @@ Array vim_get_api_info(uint64_t channel_id) return rv; } +/// Identify the client for nvim. Can be called more than once, but subsequent +/// calls will remove earlier info, which should be resent if it is still +/// valid. (This could happen if a library first identifies the channel, and a +/// plugin using that library later overrides that info) +/// +/// @param name short name for the connected client +/// @param version Dictionary describing the version, with the following +/// possible keys (all optional) +/// - "major" major version (defaults to 0 if not set, for no release yet) +/// - "minor" minor version +/// - "patch" patch number +/// - "prerelease" string describing a prerelease, like "dev" or "beta1" +/// - "commit" hash or similar identifier of commit +/// @param type Must be one of the following values. A client library should +/// use "remote" if the library user hasn't specified other value. +/// - "remote" remote client that connected to nvim. +/// - "ui" gui frontend +/// - "embedder" application using nvim as a component, for instance +/// IDE/editor implementing a vim mode. +/// - "host" plugin host, typically started by nvim +/// - "plugin" single plugin, started by nvim +/// @param methods Builtin methods in the client. For a host, this does not +/// include plugin methods which will be discovered later. +/// The key should be the method name, the values are dicts with +/// the following (optional) keys: +/// - "async" if true, send as a notification. If false or unspecified, +/// use a blocking request +/// - "nargs" Number of arguments. Could be a single integer or an array +/// two integers, minimum and maximum inclusive. +/// Further keys might be added in later versions of nvim and unknown keys +/// are thus ignored. Clients must only use keys defined in this or later +/// versions of nvim! +/// +/// @param attributes Informal attributes describing the client. Clients might +/// define their own keys, but the following are suggested: +/// - "website" Website of client (for instance github repository) +/// - "license" Informal description of the license, such as "Apache 2", +/// "GPLv3" or "MIT" +/// - "logo" URI or path to image, preferably small logo or icon. +/// .png or .svg format is preferred. +/// +void nvim_set_client_info(uint64_t channel_id, String name, + Dictionary version, String type, + Dictionary methods, Dictionary attributes, + Error *err) + FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY +{ + Dictionary info = ARRAY_DICT_INIT; + PUT(info, "name", copy_object(STRING_OBJ(name))); + + version = copy_dictionary(version); + bool has_major = false; + for (size_t i = 0; i < version.size; i++) { + if (strequal(version.items[i].key.data, "major")) { + has_major = true; + break; + } + } + if (!has_major) { + PUT(version, "major", INTEGER_OBJ(0)); + } + PUT(info, "version", DICTIONARY_OBJ(version)); + + PUT(info, "type", copy_object(STRING_OBJ(type))); + PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods))); + PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes))); + + rpc_set_client_info(channel_id, info); +} + +/// Get information about a channel. +/// +/// @returns a Dictionary, describing a channel with the +/// following keys: +/// - "stream" the stream underlying the channel +/// - "stdio" stdin and stdout of this Nvim instance +/// - "stderr" stderr of this Nvim instance +/// - "socket" TCP/IP socket or named pipe +/// - "job" job with communication over its stdio +/// - "mode" how data received on the channel is interpreted +/// - "bytes" send and recieve raw bytes +/// - "terminal" a |terminal| instance interprets ASCII sequences +/// - "rpc" |RPC| communication on the channel is active +/// - "pty" Name of pseudoterminal, if one is used (optional). +/// On a POSIX system, this will be a device path like +/// /dev/pts/1. Even if the name is unknown, the key will +/// still be present to indicate a pty is used. This is +/// currently the case when using winpty on windows. +/// - "buffer" buffer with connected |terminal| instance (optional) +/// - "client" information about the client on the other end of the +/// RPC channel, if it has added it using +/// |nvim_set_client_info()|. (optional) +/// +Dictionary nvim_get_chan_info(Integer chan, Error *err) + FUNC_API_SINCE(4) +{ + if (chan < 0) { + return (Dictionary)ARRAY_DICT_INIT; + } + return channel_info((uint64_t)chan); +} + +/// Get information about all open channels. +/// +/// @returns Array of Dictionaries, each describing a channel with +/// the format specified at |nvim_get_chan_info()|. +Array nvim_list_chans(void) + FUNC_API_SINCE(4) +{ + return channel_all_info(); +} + +/// Calls many API methods atomically. +/// +/// This has two main usages: +/// 1. To perform several requests from an async context atomically, i.e. +/// without interleaving redraws, RPC requests from other clients, or user +/// interactions (however API methods may trigger autocommands or event +/// processing which have such side-effects, e.g. |:sleep| may wake timers). +/// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. +/// +/// @param calls an array of calls, where each call is described by an array +/// with two elements: the request name, and an array of arguments. +/// @param[out] err Details of a validation error of the nvim_multi_request call +/// itself, i.e. malformed `calls` parameter. Errors from called methods will +/// be indicated in the return value, see below. +/// +/// @return an array with two elements. The first is an array of return +/// values. The second is NIL if all calls succeeded. If a call resulted in +/// an error, it is a three-element array with the zero-based index of the call +/// which resulted in an error, the error type and the error message. If an +/// error occurred, the values from all preceding calls will still be returned. +Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY +{ + Array rv = ARRAY_DICT_INIT; + Array results = ARRAY_DICT_INIT; + Error nested_error = ERROR_INIT; + + size_t i; // also used for freeing the variables + for (i = 0; i < calls.size; i++) { + if (calls.items[i].type != kObjectTypeArray) { + api_set_error(err, + kErrorTypeValidation, + "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, + kErrorTypeValidation, + "Items in calls array must be arrays of size 2"); + goto validation_error; + } + + if (call.items[0].type != kObjectTypeString) { + api_set_error(err, + 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, + kErrorTypeValidation, + "Args must be Array"); + goto validation_error; + } + Array args = call.items[1].data.array; + + MsgpackRpcRequestHandler handler = + msgpack_rpc_get_handler_for(name.data, + name.size, + &nested_error); + + if (ERROR_SET(&nested_error)) { + break; + } + Object result = handler.fn(channel_id, args, &nested_error); + if (ERROR_SET(&nested_error)) { + // error handled after loop + break; + } + + ADD(results, result); + } + + ADD(rv, ARRAY_OBJ(results)); + if (ERROR_SET(&nested_error)) { + Array errval = ARRAY_DICT_INIT; + ADD(errval, INTEGER_OBJ((Integer)i)); + ADD(errval, INTEGER_OBJ(nested_error.type)); + ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); + ADD(rv, ARRAY_OBJ(errval)); + } else { + ADD(rv, NIL); + } + 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 /// later. /// -/// @param message The message to write -/// @param to_err true if it should be treated as an error message (use -/// `emsg` instead of `msg` to print each line) +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) static void write_msg(String message, bool to_err) { static size_t out_pos = 0, err_pos = 0; @@ -650,7 +1743,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; \ } \ @@ -668,3 +1761,216 @@ 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 +/// +/// Each dictionary has the following keys: +/// - "height" requested height of the UI +/// - "width" requested width of the UI +/// - "rgb" whether the UI uses rgb colors (false implies cterm colors) +/// - "ext_..." Requested UI extensions, see |ui-options| +/// - "chan" Channel id of remote UI (not present for TUI) +/// +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; +} + +/// Selects an item in the completion popupmenu +/// +/// When insert completion is not active, this API call is silently ignored. +/// It is mostly useful for an external UI using |ui-popupmenu| for instance +/// to control the popupmenu with the mouse. But it can also be used in an +/// insert mode mapping, use <cmd> mapping |:map-cmd| to ensure the mapping +/// doesn't end completion mode. +/// +/// @param item Index of the item to select, starting with zero. Pass in "-1" +/// to select no item (restore original text). +/// @param insert Whether the selection should be inserted in the buffer. +/// @param finish If true, completion will be finished with this item, and the +/// popupmenu dissmissed. Implies `insert`. +void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, + Dictionary opts, Error *err) + FUNC_API_SINCE(6) +{ + if (opts.size > 0) { + api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); + return; + } + + if (finish) { + insert = true; + } + + pum_ext_select_item((int)item, insert, finish); +} + +/// NB: if your UI doesn't use hlstate, this will not return hlstate first time +Array nvim__inspect_cell(Integer row, Integer col, Error *err) +{ + Array ret = ARRAY_DICT_INIT; + if (row < 0 || row >= default_grid.Rows + || col < 0 || col >= default_grid.Columns) { + return ret; + } + size_t off = default_grid.line_offset[(size_t)row] + (size_t)col; + ADD(ret, STRING_OBJ(cstr_to_string((char *)default_grid.chars[off]))); + int attr = default_grid.attrs[off]; + ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err))); + // will not work first time + if (!highlight_use_hlstate()) { + ADD(ret, ARRAY_OBJ(hl_inspect(attr))); + } + return ret; +} diff --git a/src/nvim/api/vim.h b/src/nvim/api/vim.h index 5e0b35b562..d6873da6d2 100644 --- a/src/nvim/api/vim.h +++ b/src/nvim/api/vim.h @@ -4,6 +4,10 @@ #include <stdint.h> #include "nvim/api/private/defs.h" +#include "nvim/map.h" + +EXTERN Map(String, handle_T) *namespace_ids INIT(= NULL); +EXTERN handle_T next_namespace_id INIT(= 1); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.h.generated.h" diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index f644453358..33857f95b7 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> @@ -7,19 +10,20 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/vim.h" +#include "nvim/buffer.h" #include "nvim/cursor.h" #include "nvim/window.h" #include "nvim/screen.h" #include "nvim/move.h" -#include "nvim/misc2.h" /// Gets the current buffer in a window /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer handle -Buffer window_get_buffer(Window window, Error *err) +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Buffer handle +Buffer nvim_win_get_buf(Window window, Error *err) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -30,12 +34,48 @@ Buffer window_get_buffer(Window window, Error *err) return win->w_buffer->handle; } +/// Sets the current buffer in a window, without side-effects +/// +/// @param window Window handle +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +void nvim_win_set_buf(Window window, Buffer buffer, Error *err) + FUNC_API_SINCE(5) +{ + win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; + buf_T *buf = find_buffer_by_handle(buffer, err); + tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; + + if (!win || !buf) { + return; + } + + if (switch_win(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + api_set_error(err, + kErrorTypeException, + "Failed to switch to window %d", + window); + } + + try_start(); + int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0); + if (!try_end(err) && result == FAIL) { + api_set_error(err, + kErrorTypeException, + "Failed to set buffer %d", + buffer); + } + + restore_win(save_curwin, save_curtab, false); +} + /// Gets the cursor position in the window /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the (row, col) tuple -ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err) +/// @param window Window handle +/// @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) { Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); @@ -50,22 +90,23 @@ ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err) /// Sets the cursor position in the window /// -/// @param window The window handle -/// @param pos the (row, col) tuple representing the new position -/// @param[out] err Details of an error that may have occurred -void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) +/// @param window Window handle +/// @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) { 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; } @@ -73,12 +114,12 @@ void window_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; } @@ -88,18 +129,22 @@ void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // When column is out of range silently correct it. check_cursor_col_win(win); + // Make sure we stick in this column. + win->w_set_curswant = true; + // make sure cursor is in visible range even if win != curwin update_topline_win(win); - update_screen(VALID); + redraw_win_later(win, VALID); } /// Gets the window height /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the height in rows -Integer window_get_height(Window window, Error *err) +/// @param window Window handle +/// @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) { win_T *win = find_window_by_handle(window, err); @@ -113,10 +158,11 @@ Integer window_get_height(Window window, Error *err) /// Sets the window height. This will only succeed if the screen is split /// horizontally. /// -/// @param window The window handle -/// @param height the new height in rows -/// @param[out] err Details of an error that may have occurred -void window_set_height(Window window, Integer height, Error *err) +/// @param window Window handle +/// @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) { win_T *win = find_window_by_handle(window, err); @@ -125,7 +171,7 @@ void window_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; } @@ -139,10 +185,11 @@ void window_set_height(Window window, Integer height, Error *err) /// Gets the window width /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the width in columns -Integer window_get_width(Window window, Error *err) +/// @param window Window handle +/// @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) { win_T *win = find_window_by_handle(window, err); @@ -156,10 +203,11 @@ Integer window_get_width(Window window, Error *err) /// Sets the window width. This will only succeed if the screen is split /// vertically. /// -/// @param window The window handle -/// @param width the new width in columns -/// @param[out] err Details of an error that may have occurred -void window_set_width(Window window, Integer width, Error *err) +/// @param window Window handle +/// @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) { win_T *win = find_window_by_handle(window, err); @@ -168,7 +216,7 @@ void window_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; } @@ -182,11 +230,12 @@ void window_set_width(Window window, Integer width, Error *err) /// Gets a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value -Object window_get_var(Window window, String name, Error *err) +/// @param window Window handle +/// @param name Variable name +/// @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) { win_T *win = find_window_by_handle(window, err); @@ -199,11 +248,48 @@ Object window_get_var(Window window, String name, Error *err) /// Sets a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param window Window handle +/// @param name Variable name +/// @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) +{ + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return; + } + + dict_set_var(win->w_vars, name, value, false, false, err); +} + +/// Removes a window-scoped (w:) variable +/// +/// @param window Window handle +/// @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) +{ + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return; + } + + dict_set_var(win->w_vars, name, NIL, true, false, err); +} + +/// Sets a window-scoped (w:) variable +/// +/// @deprecated +/// +/// @param window Window handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @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`. @@ -215,18 +301,17 @@ Object window_set_var(Window window, String name, Object value, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(win->w_vars, name, value, false, err); + return dict_set_var(win->w_vars, name, value, false, true, err); } /// Removes a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @deprecated /// -/// @warning It may return nil if there was no previous value -/// or if previous value was `v:null`. +/// @param window Window handle +/// @param name variable name +/// @param[out] err Error details, if any +/// @return Old value Object window_del_var(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -235,16 +320,17 @@ Object window_del_var(Window window, String name, Error *err) return (Object) OBJECT_INIT; } - return dict_set_value(win->w_vars, name, NIL, true, err); + return dict_set_var(win->w_vars, name, NIL, true, true, err); } /// Gets a window option value /// -/// @param window The window handle -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value -Object window_get_option(Window window, String name, Error *err) +/// @param window Window handle +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_win_get_option(Window window, String name, Error *err) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -258,11 +344,13 @@ Object window_get_option(Window window, String name, Error *err) /// Sets a window option value. Passing 'nil' as value deletes the option(only /// works if there's a global fallback) /// -/// @param window The window handle -/// @param name The option name -/// @param value The option value -/// @param[out] err Details of an error that may have occurred -void window_set_option(Window window, String name, Object value, Error *err) +/// @param window Window handle +/// @param name Option name +/// @param value Option value +/// @param[out] err Error details, if any +void nvim_win_set_option(uint64_t channel_id, Window window, + String name, Object value, Error *err) + FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -270,15 +358,16 @@ void window_set_option(Window window, String name, Object value, Error *err) return; } - set_option_to(win, SREQ_WIN, name, value, err); + set_option_to(channel_id, win, SREQ_WIN, name, value, err); } /// Gets the window position in display cells. First position is zero. /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The (row, col) tuple with the window position -ArrayOf(Integer, 2) window_get_position(Window window, Error *err) +/// @param window Window handle +/// @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) { Array rv = ARRAY_DICT_INIT; win_T *win = find_window_by_handle(window, err); @@ -291,12 +380,13 @@ ArrayOf(Integer, 2) window_get_position(Window window, Error *err) return rv; } -/// Gets the window tab page +/// Gets the window tabpage /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The tab page that contains the window -Tabpage window_get_tabpage(Window window, Error *err) +/// @param window Window handle +/// @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) { Tabpage rv = 0; win_T *win = find_window_by_handle(window, err); @@ -308,13 +398,37 @@ Tabpage window_get_tabpage(Window window, Error *err) return rv; } +/// Gets the window number +/// +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Window number +Integer nvim_win_get_number(Window window, Error *err) + FUNC_API_SINCE(1) +{ + int rv = 0; + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return rv; + } + + int tabnr; + win_get_tabwin(window, &tabnr, &rv); + + return rv; +} + /// Checks if a window is valid /// -/// @param window The window handle +/// @param window Window handle /// @return true if the window is valid, false otherwise -Boolean window_is_valid(Window window) +Boolean nvim_win_is_valid(Window window) + 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; } |
