aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/api')
-rw-r--r--src/nvim/api/buffer.c635
-rw-r--r--src/nvim/api/private/helpers.c84
-rw-r--r--src/nvim/api/private/helpers.h20
-rw-r--r--src/nvim/api/ui.c58
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/api/vim.c270
6 files changed, 924 insertions, 147 deletions
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 3e1209d1b1..8e61976c4b 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -23,7 +23,10 @@
#include "nvim/memory.h"
#include "nvim/misc1.h"
#include "nvim/ex_cmds.h"
+#include "nvim/map_defs.h"
+#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -39,6 +42,8 @@
/// \defgroup api-buffer
///
+/// \brief For more information on buffers, see |buffers|
+///
/// Unloaded Buffers:~
///
/// Buffers may be unloaded by the |:bunload| command or the buffer's
@@ -101,25 +106,50 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv;
}
-/// Activates buffer-update events on a channel, or as lua callbacks.
+/// Activates buffer-update events on a channel, or as Lua callbacks.
+///
+/// Example (Lua): capture buffer updates in a global `events` variable
+/// (use "print(vim.inspect(events))" to see its contents):
+/// <pre>
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines=function(...) table.insert(events, {...}) end})
+/// </pre>
+///
+/// @see |nvim_buf_detach()|
+/// @see |api-buffer-updates-lua|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
-/// @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`. Not used for lua callbacks.
+/// @param send_buffer True if the initial notification should contain the
+/// whole buffer: first notification will be `nvim_buf_lines_event`.
+/// Else the first notification will be `nvim_buf_changedtick_event`.
+/// Not for Lua callbacks.
/// @param opts Optional parameters.
-/// - `on_lines`: lua callback received on change.
-/// - `on_changedtick`: lua callback received on changedtick
-/// increment without text change.
-/// - `utf_sizes`: include UTF-32 and UTF-16 size of
-/// the replaced region.
-/// See |api-buffer-updates-lua| for more information
+/// - on_lines: Lua callback invoked on change.
+/// Return `true` to detach. Args:
+/// - the string "lines"
+/// - buffer handle
+/// - b:changedtick
+/// - first line that changed (zero-indexed)
+/// - last line that was changed
+/// - last line in the updated range
+/// - byte count of previous contents
+/// - deleted_codepoints (if `utf_sizes` is true)
+/// - deleted_codeunits (if `utf_sizes` is true)
+/// - on_changedtick: Lua callback invoked on changedtick
+/// increment without text change. Args:
+/// - the string "changedtick"
+/// - buffer handle
+/// - b:changedtick
+/// - on_detach: Lua callback invoked on detach. Args:
+/// - the string "detach"
+/// - buffer handle
+/// - utf_sizes: include UTF-32 and UTF-16 size of the replaced
+/// region, as args to `on_lines`.
/// @param[out] err Error details, if any
-/// @return False when updates couldn't be enabled because the buffer isn't
-/// loaded or `opts` contained an invalid key; otherwise True.
-/// TODO: LUA_API_NO_EVAL
+/// @return False if attach failed (invalid parameter, or buffer isn't loaded);
+/// otherwise True. TODO: LUA_API_NO_EVAL
Boolean nvim_buf_attach(uint64_t channel_id,
Buffer buffer,
Boolean send_buffer,
@@ -144,21 +174,29 @@ Boolean nvim_buf_attach(uint64_t channel_id,
goto error;
}
cb.on_lines = v->data.luaref;
- v->data.integer = LUA_NOREF;
+ v->data.luaref = LUA_NOREF;
+ } else if (is_lua && strequal("_on_bytes", k.data)) {
+ // NB: undocumented, untested and incomplete interface!
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ cb.on_bytes = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("on_changedtick", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
}
cb.on_changedtick = v->data.luaref;
- v->data.integer = LUA_NOREF;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("on_detach", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
}
cb.on_detach = v->data.luaref;
- v->data.integer = LUA_NOREF;
+ v->data.luaref = LUA_NOREF;
} else if (is_lua && strequal("utf_sizes", k.data)) {
if (v->type != kObjectTypeBoolean) {
api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
@@ -176,6 +214,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
executor_free_luaref(cb.on_lines);
+ executor_free_luaref(cb.on_bytes);
executor_free_luaref(cb.on_changedtick);
executor_free_luaref(cb.on_detach);
return false;
@@ -183,13 +222,14 @@ error:
/// Deactivates buffer-update events on the channel.
///
-/// For Lua callbacks see |api-lua-detach|.
+/// @see |nvim_buf_attach()|
+/// @see |api-lua-detach| for detaching Lua callbacks
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
/// @param[out] err Error details, if any
-/// @return False when updates couldn't be disabled because the buffer
-/// isn't loaded; otherwise True.
+/// @return False if detach failed (because the buffer isn't loaded);
+/// otherwise True.
Boolean nvim_buf_detach(uint64_t channel_id,
Buffer buffer,
Error *err)
@@ -205,6 +245,90 @@ Boolean nvim_buf_detach(uint64_t channel_id,
return true;
}
+static void buf_clear_luahl(buf_T *buf, bool force)
+{
+ if (buf->b_luahl || force) {
+ executor_free_luaref(buf->b_luahl_start);
+ executor_free_luaref(buf->b_luahl_window);
+ executor_free_luaref(buf->b_luahl_line);
+ executor_free_luaref(buf->b_luahl_end);
+ }
+ buf->b_luahl_start = LUA_NOREF;
+ buf->b_luahl_window = LUA_NOREF;
+ buf->b_luahl_line = LUA_NOREF;
+ buf->b_luahl_end = LUA_NOREF;
+}
+
+/// Unstabilized interface for defining syntax hl in lua.
+///
+/// This is not yet safe for general use, lua callbacks will need to
+/// be restricted, like textlock and probably other stuff.
+///
+/// The API on_line/nvim__put_attr is quite raw and not intended to be the
+/// final shape. Ideally this should operate on chunks larger than a single
+/// line to reduce interpreter overhead, and generate annotation objects
+/// (bufhl/virttext) on the fly but using the same representation.
+void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer,
+ DictionaryOf(LuaRef) opts, Error *err)
+ FUNC_API_LUA_ONLY
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ redraw_buf_later(buf, NOT_VALID);
+ buf_clear_luahl(buf, false);
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("on_start", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ buf->b_luahl_start = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ } else if (strequal("on_window", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ buf->b_luahl_window = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ } else if (strequal("on_line", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ buf->b_luahl_line = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
+ }
+ buf->b_luahl = true;
+ return;
+error:
+ buf_clear_luahl(buf, true);
+ buf->b_luahl = false;
+}
+
+void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last,
+ Error *err)
+ FUNC_API_LUA_ONLY
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return;
+ }
+
+ redraw_buf_range_later(buf, (linenr_T)first+1, (linenr_T)last);
+}
+
/// Sets a buffer line
///
/// @deprecated use nvim_buf_set_lines instead.
@@ -529,7 +653,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- false);
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
@@ -984,6 +1108,247 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+/// Returns position for a given extmark id
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return (row, col) tuple or empty list () if extmark id was absent
+ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
+ Integer id, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return rv;
+ }
+
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (extmark.row < 0) {
+ return rv;
+ }
+ ADD(rv, INTEGER_OBJ((Integer)extmark.row));
+ ADD(rv, INTEGER_OBJ((Integer)extmark.col));
+ return rv;
+}
+
+/// Gets extmarks in "traversal order" from a |charwise| region defined by
+/// buffer positions (inclusive, 0-indexed |api-indexing|).
+///
+/// Region can be given as (row,col) tuples, or valid extmark ids (whose
+/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
+/// respectively, thus the following are equivalent:
+///
+/// <pre>
+/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// </pre>
+///
+/// If `end` is less than `start`, traversal works backwards. (Useful
+/// with `limit`, to get the first marks prior to a given position.)
+///
+/// Example:
+///
+/// <pre>
+/// local a = vim.api
+/// local pos = a.nvim_win_get_cursor(0)
+/// local ns = a.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// print(vim.inspect(ms))
+/// </pre>
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param start Start of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param end End of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// @param[out] err Error details, if any
+/// @return List of [extmark_id, row, col] tuples in "traversal order".
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
+ Object end, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return rv;
+ }
+ Integer limit = -1;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("limit", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation, "limit is not an integer");
+ return rv;
+ }
+ limit = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+ if (limit == 0) {
+ return rv;
+ } else if (limit < 0) {
+ limit = INT64_MAX;
+ }
+
+
+ bool reverse = false;
+
+ int l_row;
+ colnr_T l_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_row, &l_col, err)) {
+ return rv;
+ }
+
+ int u_row;
+ colnr_T u_col;
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_row, &u_col, err)) {
+ return rv;
+ }
+
+ if (l_row > u_row || (l_row == u_row && l_col > u_col)) {
+ reverse = true;
+ }
+
+
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row,
+ u_col, (int64_t)limit, reverse);
+
+ for (size_t i = 0; i < kv_size(marks); i++) {
+ Array mark = ARRAY_DICT_INIT;
+ ExtmarkInfo extmark = kv_A(marks, i);
+ ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id));
+ ADD(mark, INTEGER_OBJ(extmark.row));
+ ADD(mark, INTEGER_OBJ(extmark.col));
+ ADD(rv, ARRAY_OBJ(mark));
+ }
+
+ kv_destroy(marks);
+ return rv;
+}
+
+/// Creates or updates an extmark.
+///
+/// To create a new extmark, pass id=0. The extmark id will be returned.
+// To move an existing mark, pass its id.
+///
+/// It is also allowed to create a new mark by passing in a previously unused
+/// id, but the caller must then keep track of existing and unused ids itself.
+/// (Useful over RPC, to avoid waiting for the return value.)
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id, or 0 to create new
+/// @param line Line number where to place the mark
+/// @param col Column where to place the mark
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return Id of the created/updated extmark
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+ Integer line, Integer col,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return 0;
+ }
+
+ if (opts.size > 0) {
+ api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ return 0;
+ }
+
+ size_t len = 0;
+ if (line < 0 || line > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation, "line value outside range");
+ return 0;
+ } else if (line < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
+ }
+
+ if (col == -1) {
+ col = (Integer)len;
+ } else if (col < -1 || col > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "col value outside range");
+ return 0;
+ }
+
+ uint64_t id_num;
+ if (id >= 0) {
+ id_num = (uint64_t)id;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "Invalid mark id");
+ return 0;
+ }
+
+ id_num = extmark_set(buf, (uint64_t)ns_id, id_num,
+ (int)line, (colnr_T)col, kExtmarkUndo);
+
+ return (Integer)id_num;
+}
+
+/// Removes an extmark.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return true if the extmark was found, else false
+Boolean nvim_buf_del_extmark(Buffer buffer,
+ Integer ns_id,
+ Integer id,
+ Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return false;
+ }
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return false;
+ }
+
+ return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id);
+}
+
/// Adds a highlight to buffer.
///
/// Useful for plugins that dynamically generate highlights to a buffer
@@ -1015,7 +1380,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
/// @param[out] err Error details, if any
/// @return The ns_id that was used
Integer nvim_buf_add_highlight(Buffer buffer,
- Integer ns_id,
+ Integer src_id,
String hl_group,
Integer line,
Integer col_start,
@@ -1040,17 +1405,35 @@ Integer nvim_buf_add_highlight(Buffer buffer,
col_end = MAXCOL;
}
+ uint64_t ns_id = src2ns(&src_id);
+
+ if (!(line < buf->b_ml.ml_line_count)) {
+ // safety check, we can't add marks outside the range
+ 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);
+ } else {
+ return src_id;
}
- 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;
+ int end_line = (int)line;
+ if (col_end == MAXCOL) {
+ col_end = 0;
+ end_line++;
+ }
+
+ extmark_add_decoration(buf, ns_id, hlg_id,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ VIRTTEXT_EMPTY);
+ return src_id;
}
-/// Clears namespaced objects, highlights and virtual text, from a line range
+/// Clears namespaced objects (highlights, extmarks, virtual text) from
+/// a region.
///
/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
/// buffer, specify line_start=0 and line_end=-1.
@@ -1080,8 +1463,9 @@ void nvim_buf_clear_namespace(Buffer buffer,
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
-
- bufhl_clear_line_range(buf, (int)ns_id, (int)line_start+1, (int)line_end);
+ extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
+ (int)line_start, 0,
+ (int)line_end-1, MAXCOL);
}
/// Clears highlights and virtual text from namespace and range of lines
@@ -1104,6 +1488,43 @@ void nvim_buf_clear_highlight(Buffer buffer,
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
}
+static VirtText parse_virt_text(Array chunks, Error *err)
+{
+ 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 }));
+ }
+
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
+
/// Set the virtual text (annotation) for a buffer line.
///
@@ -1133,7 +1554,7 @@ void nvim_buf_clear_highlight(Buffer buffer,
/// @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 src_id,
Integer line,
Array chunks,
Dictionary opts,
@@ -1155,41 +1576,129 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
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;
- }
+ uint64_t ns_id = src2ns(&src_id);
- String str = chunk.items[0].data.string;
- char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+ VirtText virt_text = parse_virt_text(chunks, err);
+ if (ERROR_SET(err)) {
+ return 0;
+ }
- 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);
- }
+
+ VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id);
+
+ if (existing) {
+ clear_virttext(existing);
+ *existing = virt_text;
+ return src_id;
+ }
+
+ extmark_add_decoration(buf, ns_id, 0,
+ (int)line, 0, -1, -1,
+ virt_text);
+ return src_id;
+}
+
+/// Get the virtual text (annotation) for a buffer line.
+///
+/// The virtual text is returned as list of lists, whereas the inner lists have
+/// either one or two elements. The first element is the actual text, the
+/// optional second element is the highlight group.
+///
+/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+///
+/// If there is no virtual text associated with the given line, an empty list
+/// is returned.
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param line Line to get the virtual text from (zero-indexed)
+/// @param[out] err Error details, if any
+/// @return List of virtual text chunks
+Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err)
+ FUNC_API_SINCE(7)
+{
+ Array chunks = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return chunks;
+ }
+
+ if (line < 0 || line >= MAXLNUM) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return chunks;
+ }
+
+ VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0);
+
+ if (!virt_text) {
+ return chunks;
+ }
+
+ for (size_t i = 0; i < virt_text->size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &virt_text->items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk, STRING_OBJ(cstr_to_string(
+ (const char *)syn_id2name(vtc->hl_id))));
}
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+
+ return chunks;
+}
+
+Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
+ Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col,
+ Array virt_text,
+ Error *err)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+
+ if (!ns_initialized((uint64_t)ns_id)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ return 0;
}
- 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;
+ if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ return 0;
+ }
+
+ if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ return 0;
+ }
+ if (end_row < 0 || end_col < 0) {
+ end_row = -1;
+ end_col = -1;
+ }
+
+ if (start_row >= buf->b_ml.ml_line_count
+ || end_row >= buf->b_ml.ml_line_count) {
+ // safety check, we can't add marks outside the range
+ return 0;
+ }
+
+ int hlg_id = 0;
+ if (hl_group.size > 0) {
+ hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ }
+
+ VirtText vt = parse_virt_text(virt_text, err);
+ if (ERROR_SET(err)) {
+ return 0;
+ }
+
+ uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id,
+ (int)start_row, (colnr_T)start_col,
+ (int)end_row, (colnr_T)end_col, vt);
+ return (Integer)mark_id;
}
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
@@ -1213,6 +1722,16 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ u_header_T *uhp = NULL;
+ if (buf->b_u_curhead != NULL) {
+ uhp = buf->b_u_curhead;
+ } else if (buf->b_u_newhead) {
+ uhp = buf->b_u_newhead;
+ }
+ if (uhp) {
+ PUT(rv, "uhp_extmark_size", INTEGER_OBJ((Integer)kv_size(uhp->uh_extmark)));
+ }
+
return rv;
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 2056cb07e3..f194b6b474 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -10,6 +10,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/handle.h"
+#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
@@ -23,6 +24,7 @@
#include "nvim/eval/typval.h"
#include "nvim/map_defs.h"
#include "nvim/map.h"
+#include "nvim/extmark.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -629,7 +631,7 @@ buf_T *find_buffer_by_handle(Buffer buffer, Error *err)
buf_T *rv = handle_get_buffer(buffer);
if (!rv) {
- api_set_error(err, kErrorTypeValidation, "Invalid buffer id");
+ api_set_error(err, kErrorTypeValidation, "Invalid buffer id: %d", buffer);
}
return rv;
@@ -644,7 +646,7 @@ win_T *find_window_by_handle(Window window, Error *err)
win_T *rv = handle_get_window(window);
if (!rv) {
- api_set_error(err, kErrorTypeValidation, "Invalid window id");
+ api_set_error(err, kErrorTypeValidation, "Invalid window id: %d", window);
}
return rv;
@@ -659,7 +661,7 @@ tabpage_T *find_tab_by_handle(Tabpage tabpage, Error *err)
tabpage_T *rv = handle_get_tabpage(tabpage);
if (!rv) {
- api_set_error(err, kErrorTypeValidation, "Invalid tabpage id");
+ api_set_error(err, kErrorTypeValidation, "Invalid tabpage id: %d", tabpage);
}
return rv;
@@ -1073,8 +1075,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
break;
case kObjectTypeBoolean:
- tv->v_type = VAR_SPECIAL;
- tv->vval.v_special = obj.data.boolean? kSpecialVarTrue: kSpecialVarFalse;
+ tv->v_type = VAR_BOOL;
+ tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
break;
case kObjectTypeBuffer:
@@ -1363,6 +1365,9 @@ Dictionary copy_dictionary(Dictionary dict)
Object copy_object(Object obj)
{
switch (obj.type) {
+ case kObjectTypeBuffer:
+ case kObjectTypeTabpage:
+ case kObjectTypeWindow:
case kObjectTypeNil:
case kObjectTypeBoolean:
case kObjectTypeInteger:
@@ -1505,3 +1510,72 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
return mappings;
}
+
+// Is the Namespace in use?
+bool ns_initialized(uint64_t ns)
+{
+ if (ns < 1) {
+ return false;
+ }
+ return ns < (uint64_t)next_namespace_id;
+}
+
+/// Gets the line and column of an extmark.
+///
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
+///
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
+ *row, colnr_T *col, Error *err)
+{
+ // Check if it is mark id
+ if (obj.type == kObjectTypeInteger) {
+ Integer id = obj.data.integer;
+ if (id == 0) {
+ *row = 0;
+ *col = 0;
+ return true;
+ } else if (id == -1) {
+ *row = MAXLNUM;
+ *col = MAXCOL;
+ return true;
+ } else if (id < 0) {
+ api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
+ return false;
+ }
+
+ ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
+ if (extmark.row >= 0) {
+ *row = extmark.row;
+ *col = extmark.col;
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "No mark with requested id");
+ return false;
+ }
+
+ // Check if it is a position
+ } else if (obj.type == kObjectTypeArray) {
+ Array pos = obj.data.array;
+ if (pos.size != 2
+ || pos.items[0].type != kObjectTypeInteger
+ || pos.items[1].type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "Position must have 2 integer elements");
+ return false;
+ }
+ Integer pos_row = pos.items[0].data.integer;
+ Integer pos_col = pos.items[1].data.integer;
+ *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
+ *col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
+ return true;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Position must be a mark id Integer or position Array");
+ return false;
+ }
+}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 0ea7667428..df3a263dcf 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -60,6 +60,12 @@
#define ADD(array, item) \
kv_push(array, item)
+#define FIXED_TEMP_ARRAY(name, fixsize) \
+ Array name = ARRAY_DICT_INIT; \
+ Object name##__items[fixsize]; \
+ name.size = fixsize; \
+ name.items = name##__items; \
+
#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1})
/// Create a new String instance, putting data in allocated memory
@@ -102,6 +108,20 @@ typedef struct {
int did_emsg;
} TryState;
+// `msg_list` controls the collection of abort-causing non-exception errors,
+// which would otherwise be ignored. This pattern is from do_cmdline().
+//
+// TODO(bfredl): prepare error-handling at "top level" (nv_event).
+#define TRY_WRAP(code) \
+ do { \
+ struct msglist **saved_msg_list = msg_list; \
+ struct msglist *private_msg_list; \
+ msg_list = &private_msg_list; \
+ private_msg_list = NULL; \
+ code \
+ msg_list = saved_msg_list; /* Restore the exception context. */ \
+ } while (0)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
#endif
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 75ee05761b..717713b948 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -109,7 +109,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_height = 0;
+ ui->pum_nlines = 0;
+ ui->pum_pos = false;
+ ui->pum_width = 0.0;
+ ui->pum_height = 0.0;
+ ui->pum_row = -1.0;
+ ui->pum_col = -1.0;
ui->rgb = true;
ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
@@ -340,7 +345,56 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
"It must support the ext_popupmenu option");
return;
}
- ui->pum_height = (int)height;
+
+ ui->pum_nlines = (int)height;
+}
+
+/// Tells Nvim the geometry of the popumenu, to align floating windows with an
+/// external popup menu.
+///
+/// Note that this method is not to be confused with |nvim_ui_pum_set_height()|,
+/// which sets the number of visible items in the popup menu, while this
+/// function sets the bounding box of the popup menu, including visual
+/// decorations such as boarders and sliders. Floats need not use the same font
+/// size, nor be anchored to exact grid corners, so one can set floating-point
+/// numbers to the popup menu geometry.
+///
+/// @param channel_id
+/// @param width Popupmenu width.
+/// @param height Popupmenu height.
+/// @param row Popupmenu row.
+/// @param col Popupmenu height.
+/// @param[out] err Error details, if any.
+void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height,
+ Float row, Float col, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
+ }
+
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ if (!ui->ui_ext[kUIPopupmenu]) {
+ api_set_error(err, kErrorTypeValidation,
+ "UI must support the ext_popupmenu option");
+ return;
+ }
+
+ if (width <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected width > 0");
+ return;
+ } else if (height <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected height > 0");
+ return;
+ }
+
+ ui->pum_row = (double)row;
+ ui->pum_col = (double)col;
+ ui->pum_width = (double)width;
+ ui->pum_height = (double)height;
+ ui->pum_pos = true;
}
/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 6677e248cf..ab31db39e9 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -115,6 +115,10 @@ void win_close(Integer grid)
void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
+void win_viewport(Integer grid, Window win, Integer topline,
+ Integer botline, Integer curline, Integer curcol)
+ FUNC_API_SINCE(7) 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;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 602733fd31..d6f95c7a5f 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -25,6 +25,7 @@
#include "nvim/highlight.h"
#include "nvim/window.h"
#include "nvim/types.h"
+#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/screen.h"
#include "nvim/memline.h"
@@ -35,10 +36,12 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/fileio.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
+#include "nvim/extmark.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -53,20 +56,6 @@
# include "api/vim.c.generated.h"
#endif
-// `msg_list` controls the collection of abort-causing non-exception errors,
-// which would otherwise be ignored. This pattern is from do_cmdline().
-//
-// TODO(bfredl): prepare error-handling at "top level" (nv_event).
-#define TRY_WRAP(code) \
- do { \
- struct msglist **saved_msg_list = msg_list; \
- struct msglist *private_msg_list; \
- msg_list = &private_msg_list; \
- private_msg_list = NULL; \
- code \
- msg_list = saved_msg_list; /* Restore the exception context. */ \
- } while (0)
-
void api_vim_init(void)
FUNC_API_NOEXPORT
{
@@ -85,10 +74,70 @@ void api_vim_free_all_mem(void)
map_free(String, handle_T)(namespace_ids);
}
+/// Executes Vimscript (multiline block of Ex-commands), like anonymous
+/// |:source|.
+///
+/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
+/// etc.
+///
+/// On execution error: fails with VimL error, does not update v:errmsg.
+///
+/// @see |execute()|
+/// @see |nvim_command()|
+///
+/// @param src Vimscript code
+/// @param output Capture and return all (non-error, non-shell |:!|) output
+/// @param[out] err Error details (Vim error), if any
+/// @return Output (non-error, non-shell |:!|) if `output` is true,
+/// else empty string.
+String nvim_exec(String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+{
+ const int save_msg_silent = msg_silent;
+ garray_T *const save_capture_ga = capture_ga;
+ garray_T capture_local;
+ if (output) {
+ ga_init(&capture_local, 1, 80);
+ capture_ga = &capture_local;
+ }
+
+ try_start();
+ msg_silent++;
+ do_source_str(src.data, "nvim_exec()");
+ capture_ga = save_capture_ga;
+ msg_silent = save_msg_silent;
+ try_end(err);
+
+ if (ERROR_SET(err)) {
+ goto theend;
+ }
+
+ if (output && 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 - 1);
+ s.data[s.size - 1] = '\0';
+ s.size = s.size - 1;
+ }
+ return s; // Caller will free the memory.
+ }
+theend:
+ if (output) {
+ ga_clear(&capture_local);
+ }
+ return (String)STRING_INIT;
+}
+
/// Executes an ex-command.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
///
+/// @see |nvim_exec()|
+///
/// @param command Ex-command string
/// @param[out] err Error details (Vim error), if any
void nvim_command(String command, Error *err)
@@ -141,10 +190,29 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err)
return hl_get_attr_by_id(attrcode, rgb, err);
}
+/// Gets a highlight group by name
+///
+/// similar to |hlID()|, but allocates a new ID if not present.
+Integer nvim_get_hl_id_by_name(String name)
+ FUNC_API_SINCE(7)
+{
+ return syn_check_group((const char_u *)name.data, (int)name.size);
+}
+
/// 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.
+//
+// If you need to input sequences like <C-o> use nvim_replace_termcodes
+// to replace the termcodes and then pass the resulting string to
+// nvim_feedkeys. You'll also want to enable escape_csi.
+///
+/// Example:
+/// <pre>
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:true)
+/// </pre>
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
@@ -184,19 +252,19 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
keys_esc = keys.data;
}
ins_typebuf((char_u *)keys_esc, (remap ? REMAP_YES : REMAP_NONE),
- insert ? 0 : typebuf.tb_len, !typed, false);
+ insert ? 0 : typebuf.tb_len, !typed, false);
+ if (vgetc_busy) {
+ typebuf_was_filled = true;
+ }
if (escape_csi) {
xfree(keys_esc);
}
- if (vgetc_busy) {
- typebuf_was_filled = true;
- }
if (execute) {
int save_msg_scroll = msg_scroll;
- /* Avoid a 1 second delay when the keys start Insert mode. */
+ // Avoid a 1 second delay when the keys start Insert mode.
msg_scroll = false;
if (!dangerous) {
ex_normal_busy++;
@@ -278,7 +346,7 @@ void nvim_input_mouse(String button, String action, String modifier,
if (strequal(action.data, "down")) {
code = KE_MOUSEUP;
} else if (strequal(action.data, "up")) {
- code = KE_MOUSEDOWN;
+ // code = KE_MOUSEDOWN
} else if (strequal(action.data, "left")) {
code = KE_MOUSERIGHT;
} else if (strequal(action.data, "right")) {
@@ -345,53 +413,16 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt,
return cstr_as_string(ptr);
}
-/// 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
+/// @deprecated
+/// @see nvim_exec
String nvim_command_output(String command, Error *err)
FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(7)
{
- 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 (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 - 1);
- s.data[s.size - 1] = '\0';
- s.size = s.size - 1;
- }
- return s; // Caller will free the memory.
- }
-
-theend:
- ga_clear(&capture_local);
- return (String)STRING_INIT;
+ return nvim_exec(command, true, err);
}
-/// Evaluates a VimL expression (:help expression).
+/// Evaluates a VimL |expression|.
/// Dictionaries and Lists are recursively expanded.
///
/// On execution error: fails with VimL error, does not update v:errmsg.
@@ -423,7 +454,8 @@ Object nvim_eval(String expr, Error *err)
if (!try_end(err)) {
if (ok == FAIL) {
// Should never happen, try_end() should get the error. #8371
- api_set_error(err, kErrorTypeException, "Failed to evaluate expression");
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate expression: '%.*s'", 256, expr.data);
} else {
rv = vim_to_object(&rettv);
}
@@ -436,6 +468,16 @@ Object nvim_eval(String expr, Error *err)
return rv;
}
+/// @deprecated Use nvim_exec_lua() instead.
+/// @see nvim_exec_lua
+Object nvim_execute_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(7)
+ FUNC_API_REMOTE_ONLY
+{
+ return executor_exec_lua_api(code, args, err);
+}
+
/// Execute Lua code. Parameters (if any) are available as `...` inside the
/// chunk. The chunk can return a value.
///
@@ -448,8 +490,9 @@ Object nvim_eval(String expr, Error *err)
/// 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
+Object nvim_exec_lua(String code, Array args, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_REMOTE_ONLY
{
return executor_exec_lua_api(code, args, err);
}
@@ -671,6 +714,40 @@ ArrayOf(String) nvim_list_runtime_paths(void)
return rv;
}
+/// Find files in runtime directories
+///
+/// 'name' can contain wildcards. For example
+/// nvim_get_runtime_file("colors/*.vim", true) will return all color
+/// scheme files.
+///
+/// It is not an error to not find any files. An empty array is returned then.
+///
+/// @param name pattern of files to search for
+/// @param all whether to return all matches or only the first
+/// @return list of absolute paths to the found files
+ArrayOf(String) nvim_get_runtime_file(String name, Boolean all)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+ if (!name.data) {
+ return rv;
+ }
+ int flags = DIP_START | (all ? DIP_ALL : 0);
+ do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv);
+ return rv;
+}
+
+static void find_runtime_cb(char_u *fname, void *cookie)
+{
+ Array *rv = (Array *)cookie;
+ ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+}
+
+String nvim__get_lib_dir(void)
+{
+ return cstr_as_string(get_lib_dir());
+}
+
/// Changes the global working directory.
///
/// @param dir Directory path
@@ -970,7 +1047,7 @@ void nvim_set_current_win(Window window, Error *err)
///
/// @param listed Sets 'buflisted'
/// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work
-/// (always 'nomodified')
+/// (always 'nomodified'). Also sets 'nomodeline' on the buffer.
/// @param[out] err Error details, if any
/// @return Buffer handle, or 0 on error
///
@@ -1000,9 +1077,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
if (scratch) {
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
- set_option_value("bh", 0L, "hide", OPT_LOCAL);
- set_option_value("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
+ set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
+ set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
+ set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
aucmd_restbuf(&aco);
}
return buf->b_fnum;
@@ -1054,10 +1132,9 @@ fail:
/// @param enter Enter the window (make it the current window)
/// @param config Map defining the window configuration. Keys:
/// - `relative`: Sets the window layout to "floating", placed at (row,col)
-/// coordinates relative to one of:
+/// coordinates relative to:
/// - "editor" The global editor grid
-/// - "win" Window given by the `win` field, or current window by
-/// default.
+/// - "win" Window given by the `win` field, or current window.
/// - "cursor" Cursor position in current window.
/// - `win`: |window-ID| for relative="win".
/// - `anchor`: Decides which corner of the float to place at (row,col):
@@ -1088,9 +1165,10 @@ fail:
/// float where the text should not be edited. Disables
/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
/// 'foldcolumn', 'spell' and 'list' options. 'signcolumn'
-/// is changed to `auto`. The end-of-buffer region is hidden
-/// by setting `eob` flag of 'fillchars' to a space char,
-/// and clearing the |EndOfBuffer| region in 'winhighlight'.
+/// is changed to `auto` and 'colorcolumn' is cleared. The
+/// end-of-buffer region is hidden by setting `eob` flag of
+/// 'fillchars' to a space char, and clearing the
+/// |EndOfBuffer| region in 'winhighlight'.
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
@@ -1109,6 +1187,10 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
if (enter) {
win_enter(wp, false);
}
+ if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException, "Window was closed immediately");
+ return 0;
+ }
if (buffer > 0) {
nvim_win_set_buf(wp->handle, buffer, err);
}
@@ -1260,13 +1342,13 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
Array lines = string_to_array(data, crlf);
ADD(args, ARRAY_OBJ(lines));
ADD(args, INTEGER_OBJ(phase));
- rv = nvim_execute_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
- err);
+ rv = nvim_exec_lua(STATIC_CSTR_AS_STRING("return vim.paste(...)"), args,
+ err);
if (ERROR_SET(err)) {
draining = true;
goto theend;
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 1)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 1)) {
ResetRedobuff();
AppendCharToRedobuff('a'); // Dot-repeat.
}
@@ -1284,7 +1366,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
}
}
}
- if (!(State & CMDLINE) && !(State & INSERT) && (phase == -1 || phase == 3)) {
+ if (!(State & (CMDLINE | INSERT)) && (phase == -1 || phase == 3)) {
AppendCharToRedobuff(ESC); // Dot-repeat.
}
theend:
@@ -1304,7 +1386,7 @@ theend:
/// @param lines |readfile()|-style list of lines. |channel-lines|
/// @param type Edit behavior: any |getregtype()| result, or:
/// - "b" |blockwise-visual| mode (may include width, e.g. "b3")
-/// - "c" |characterwise| mode
+/// - "c" |charwise| mode
/// - "l" |linewise| mode
/// - "" guess by contents, see |setreg()|
/// @param after Insert after cursor (like |p|), or before (like |P|).
@@ -2395,7 +2477,7 @@ Array nvim_get_proc_children(Integer pid, Error *err)
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);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2441,7 +2523,7 @@ Object nvim_get_proc(Integer pid, Error *err)
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);
+ Object o = nvim_exec_lua(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
@@ -2521,3 +2603,27 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
}
return ret;
}
+
+/// Set attrs in nvim__buf_set_lua_hl callbacks
+///
+/// TODO(bfredl): This is rather pedestrian. The final
+/// interface should probably be derived from a reformed
+/// bufhl/virttext interface with full support for multi-line
+/// ranges etc
+void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col)
+ FUNC_API_LUA_ONLY
+{
+ if (!lua_attr_active) {
+ return;
+ }
+ if (id == 0 || syn_get_final_id((int)id) == 0) {
+ return;
+ }
+ int attr = syn_id2attr((int)id);
+ if (attr == 0) {
+ return;
+ }
+ decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
+ (int)end_row, (colnr_T)end_col);
+}