aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/buffer.c905
-rw-r--r--src/nvim/api/dispatch_deprecated.lua69
-rw-r--r--src/nvim/api/private/defs.h47
-rw-r--r--src/nvim/api/private/dispatch.c52
-rw-r--r--src/nvim/api/private/dispatch.h23
-rw-r--r--src/nvim/api/private/handle.c19
-rw-r--r--src/nvim/api/private/handle.h6
-rw-r--r--src/nvim/api/private/helpers.c688
-rw-r--r--src/nvim/api/private/helpers.h32
-rw-r--r--src/nvim/api/tabpage.c132
-rw-r--r--src/nvim/api/ui.c617
-rw-r--r--src/nvim/api/ui_events.in.h130
-rw-r--r--src/nvim/api/vim.c1702
-rw-r--r--src/nvim/api/vim.h4
-rw-r--r--src/nvim/api/window.c276
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;
}