mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
refactor(api): move extmark API to its own file
This commit is contained in:
@@ -32,7 +32,6 @@
|
||||
#include "nvim/misc1.h"
|
||||
#include "nvim/move.h"
|
||||
#include "nvim/ops.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/vim.h"
|
||||
#include "nvim/window.h"
|
||||
@@ -1238,702 +1237,6 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
|
||||
return rv;
|
||||
}
|
||||
|
||||
static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
|
||||
{
|
||||
Array rv = ARRAY_DICT_INIT;
|
||||
if (id) {
|
||||
ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id));
|
||||
}
|
||||
ADD(rv, INTEGER_OBJ(extmark.row));
|
||||
ADD(rv, INTEGER_OBJ(extmark.col));
|
||||
|
||||
if (add_dict) {
|
||||
Dictionary dict = ARRAY_DICT_INIT;
|
||||
|
||||
if (extmark.end_row >= 0) {
|
||||
PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
|
||||
PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
|
||||
}
|
||||
|
||||
if (extmark.decor) {
|
||||
Decoration *decor = extmark.decor;
|
||||
if (decor->hl_id) {
|
||||
String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
|
||||
PUT(dict, "hl_group", STRING_OBJ(name));
|
||||
}
|
||||
if (kv_size(decor->virt_text)) {
|
||||
Array chunks = ARRAY_DICT_INIT;
|
||||
for (size_t i = 0; i < decor->virt_text.size; i++) {
|
||||
Array chunk = ARRAY_DICT_INIT;
|
||||
VirtTextChunk *vtc = &decor->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))));
|
||||
}
|
||||
ADD(chunks, ARRAY_OBJ(chunk));
|
||||
}
|
||||
PUT(dict, "virt_text", ARRAY_OBJ(chunks));
|
||||
}
|
||||
|
||||
PUT(dict, "priority", INTEGER_OBJ(decor->priority));
|
||||
}
|
||||
|
||||
if (dict.size) {
|
||||
ADD(rv, DICTIONARY_OBJ(dict));
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/// Gets the position (0-indexed) of an extmark.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param id Extmark id
|
||||
/// @param opts Optional parameters. Keys:
|
||||
/// - details: Whether to include the details dict
|
||||
/// @param[out] err Error details, if any
|
||||
/// @return 0-indexed (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, 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;
|
||||
}
|
||||
|
||||
bool details = false;
|
||||
for (size_t i = 0; i < opts.size; i++) {
|
||||
String k = opts.items[i].key;
|
||||
Object *v = &opts.items[i].value;
|
||||
if (strequal("details", k.data)) {
|
||||
if (v->type == kObjectTypeBoolean) {
|
||||
details = v->data.boolean;
|
||||
} else if (v->type == kObjectTypeInteger) {
|
||||
details = v->data.integer;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "details is not an boolean");
|
||||
return rv;
|
||||
}
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
|
||||
if (extmark.row < 0) {
|
||||
return rv;
|
||||
}
|
||||
return extmark_to_array(extmark, false, details);
|
||||
}
|
||||
|
||||
/// 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: a 0-indexed (row, col) or valid extmark id
|
||||
/// (whose position defines the bound). |api-indexing|
|
||||
/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
|
||||
/// extmark id (whose position defines the bound). |api-indexing|
|
||||
/// @param opts Optional parameters. Keys:
|
||||
/// - limit: Maximum number of marks to return
|
||||
/// - details Whether to include the details dict
|
||||
/// @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;
|
||||
bool details = false;
|
||||
|
||||
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 if (strequal("details", k.data)) {
|
||||
if (v->type == kObjectTypeBoolean) {
|
||||
details = v->data.boolean;
|
||||
} else if (v->type == kObjectTypeInteger) {
|
||||
details = v->data.integer;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "details is not an boolean");
|
||||
return rv;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
||||
ExtmarkInfoArray 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++) {
|
||||
ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details)));
|
||||
}
|
||||
|
||||
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.)
|
||||
///
|
||||
/// Using the optional arguments, it is possible to use this to highlight
|
||||
/// a range of text, and also to associate virtual text to the mark.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param line Line where to place the mark, 0-based. |api-indexing|
|
||||
/// @param col Column where to place the mark, 0-based. |api-indexing|
|
||||
/// @param opts Optional parameters.
|
||||
/// - id : id of the extmark to edit.
|
||||
/// - end_line : ending line of the mark, 0-based inclusive.
|
||||
/// - end_col : ending col of the mark, 0-based exclusive.
|
||||
/// - hl_group : name of the highlight group used to highlight
|
||||
/// this mark.
|
||||
/// - hl_eol : when true, for a multiline highlight covering the
|
||||
/// EOL of a line, continue the highlight for the rest
|
||||
/// of the screen line (just like for diff and
|
||||
/// cursorline highlight).
|
||||
/// - virt_text : virtual text to link to this mark.
|
||||
/// A list of [text, highlight] tuples, each representing a
|
||||
/// text chunk with specified highlight. `highlight` element
|
||||
/// can either be a a single highlight group, or an array of
|
||||
/// multiple highlight groups that will be stacked
|
||||
/// (highest priority last). A highlight group can be supplied
|
||||
/// either as a string or as an integer, the latter which
|
||||
/// can be obtained using |nvim_get_hl_id_by_name|.
|
||||
/// - virt_text_pos : position of virtual text. Possible values:
|
||||
/// - "eol": right after eol character (default)
|
||||
/// - "overlay": display over the specified column, without
|
||||
/// shifting the underlying text.
|
||||
/// - "right_align": display right aligned in the window.
|
||||
/// - virt_text_win_col : position the virtual text at a fixed
|
||||
/// window column (starting from the first
|
||||
/// text column)
|
||||
/// - virt_text_hide : hide the virtual text when the background
|
||||
/// text is selected or hidden due to
|
||||
/// horizontal scroll 'nowrap'
|
||||
/// - hl_mode : control how highlights are combined with the
|
||||
/// highlights of the text. Currently only affects
|
||||
/// virt_text highlights, but might affect `hl_group`
|
||||
/// in later versions.
|
||||
/// - "replace": only show the virt_text color. This is the
|
||||
/// default
|
||||
/// - "combine": combine with background text color
|
||||
/// - "blend": blend with background text color.
|
||||
///
|
||||
/// - virt_lines : virtual lines to add next to this mark
|
||||
/// This should be an array over lines, where each line in
|
||||
/// turn is an array over [text, highlight] tuples. In
|
||||
/// general, buffer and window options do not affect the
|
||||
/// display of the text. In particular 'wrap'
|
||||
/// and 'linebreak' options do not take effect, so
|
||||
/// the number of extra screen lines will always match
|
||||
/// the size of the array. However the 'tabstop' buffer
|
||||
/// option is still used for hard tabs. By default lines are
|
||||
/// placed below the buffer line containing the mark.
|
||||
///
|
||||
/// - virt_lines_above: place virtual lines above instead.
|
||||
/// - virt_lines_leftcol: Place extmarks in the leftmost
|
||||
/// column of the window, bypassing
|
||||
/// sign and number columns.
|
||||
///
|
||||
/// - ephemeral : for use with |nvim_set_decoration_provider|
|
||||
/// callbacks. The mark will only be used for the current
|
||||
/// redraw cycle, and not be permantently stored in the
|
||||
/// buffer.
|
||||
/// - right_gravity : boolean that indicates the direction
|
||||
/// the extmark will be shifted in when new text is inserted
|
||||
/// (true for right, false for left). defaults to true.
|
||||
/// - end_right_gravity : boolean that indicates the direction
|
||||
/// the extmark end position (if it exists) will be shifted
|
||||
/// in when new text is inserted (true for right, false
|
||||
/// for left). Defaults to false.
|
||||
/// - priority: a priority value for the highlight group. For
|
||||
/// example treesitter highlighting uses a value of 100.
|
||||
/// @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 line, Integer col,
|
||||
Dict(set_extmark) *opts, Error *err)
|
||||
FUNC_API_SINCE(7)
|
||||
{
|
||||
Decoration decor = DECORATION_INIT;
|
||||
|
||||
buf_T *buf = find_buffer_by_handle(buffer, err);
|
||||
if (!buf) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ns_initialized((uint64_t)ns_id)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
|
||||
goto error;
|
||||
}
|
||||
|
||||
uint64_t id = 0;
|
||||
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
|
||||
id = (uint64_t)opts->id.data.integer;
|
||||
} else if (HAS_KEY(opts->id)) {
|
||||
api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int line2 = -1;
|
||||
if (opts->end_line.type == kObjectTypeInteger) {
|
||||
Integer val = opts->end_line.data.integer;
|
||||
if (val < 0 || val > buf->b_ml.ml_line_count) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_line value outside range");
|
||||
goto error;
|
||||
} else {
|
||||
line2 = (int)val;
|
||||
}
|
||||
} else if (HAS_KEY(opts->end_line)) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_line is not an integer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
colnr_T col2 = -1;
|
||||
if (opts->end_col.type == kObjectTypeInteger) {
|
||||
Integer val = opts->end_col.data.integer;
|
||||
if (val < 0 || val > MAXCOL) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_col value outside range");
|
||||
goto error;
|
||||
} else {
|
||||
col2 = (int)val;
|
||||
}
|
||||
} else if (HAS_KEY(opts->end_col)) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (HAS_KEY(opts->hl_group)) {
|
||||
decor.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->virt_text.type == kObjectTypeArray) {
|
||||
decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
|
||||
&decor.virt_text_width);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (HAS_KEY(opts->virt_text)) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (opts->virt_text_pos.type == kObjectTypeString) {
|
||||
String str = opts->virt_text_pos.data.string;
|
||||
if (strequal("eol", str.data)) {
|
||||
decor.virt_text_pos = kVTEndOfLine;
|
||||
} else if (strequal("overlay", str.data)) {
|
||||
decor.virt_text_pos = kVTOverlay;
|
||||
} else if (strequal("right_align", str.data)) {
|
||||
decor.virt_text_pos = kVTRightAlign;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
|
||||
goto error;
|
||||
}
|
||||
} else if (HAS_KEY(opts->virt_text_pos)) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (opts->virt_text_win_col.type == kObjectTypeInteger) {
|
||||
decor.col = (int)opts->virt_text_win_col.data.integer;
|
||||
decor.virt_text_pos = kVTWinCol;
|
||||
} else if (HAS_KEY(opts->virt_text_win_col)) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"virt_text_win_col is not a Number of the correct size");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#define OPTION_TO_BOOL(target, name, val) \
|
||||
target = api_object_to_bool(opts-> name, #name, val, err); \
|
||||
if (ERROR_SET(err)) { \
|
||||
goto error; \
|
||||
}
|
||||
|
||||
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
|
||||
OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
|
||||
|
||||
if (opts->hl_mode.type == kObjectTypeString) {
|
||||
String str = opts->hl_mode.data.string;
|
||||
if (strequal("replace", str.data)) {
|
||||
decor.hl_mode = kHlModeReplace;
|
||||
} else if (strequal("combine", str.data)) {
|
||||
decor.hl_mode = kHlModeCombine;
|
||||
} else if (strequal("blend", str.data)) {
|
||||
decor.hl_mode = kHlModeBlend;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"virt_text_pos: invalid value");
|
||||
goto error;
|
||||
}
|
||||
} else if (HAS_KEY(opts->hl_mode)) {
|
||||
api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool virt_lines_leftcol = false;
|
||||
OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
|
||||
|
||||
if (opts->virt_lines.type == kObjectTypeArray) {
|
||||
Array a = opts->virt_lines.data.array;
|
||||
for (size_t j = 0; j < a.size; j++) {
|
||||
if (a.items[j].type != kObjectTypeArray) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
|
||||
goto error;
|
||||
}
|
||||
int dummig;
|
||||
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
|
||||
kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
} else if (HAS_KEY(opts->virt_lines)) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
|
||||
|
||||
if (opts->priority.type == kObjectTypeInteger) {
|
||||
Integer val = opts->priority.data.integer;
|
||||
|
||||
if (val < 0 || val > UINT16_MAX) {
|
||||
api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
|
||||
goto error;
|
||||
}
|
||||
decor.priority = (DecorPriority)val;
|
||||
} else if (HAS_KEY(opts->priority)) {
|
||||
api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool right_gravity = true;
|
||||
OPTION_TO_BOOL(right_gravity, right_gravity, true);
|
||||
|
||||
// Only error out if they try to set end_right_gravity without
|
||||
// setting end_col or end_line
|
||||
if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"cannot set end_right_gravity without setting end_line or end_col");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool end_right_gravity = false;
|
||||
OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
bool ephemeral = false;
|
||||
OPTION_TO_BOOL(ephemeral, ephemeral, false);
|
||||
|
||||
if (line < 0 || line > buf->b_ml.ml_line_count) {
|
||||
api_set_error(err, kErrorTypeValidation, "line value outside range");
|
||||
goto error;
|
||||
} else if (line < buf->b_ml.ml_line_count) {
|
||||
len = ephemeral ? MAXCOL : 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");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (col2 >= 0) {
|
||||
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
|
||||
len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
|
||||
} else if (line2 == buf->b_ml.ml_line_count) {
|
||||
// We are trying to add an extmark past final newline
|
||||
len = 0;
|
||||
} else {
|
||||
// reuse len from before
|
||||
line2 = (int)line;
|
||||
}
|
||||
if (col2 > (Integer)len) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_col value outside range");
|
||||
goto error;
|
||||
}
|
||||
} else if (line2 >= 0) {
|
||||
col2 = 0;
|
||||
}
|
||||
|
||||
Decoration *d = NULL;
|
||||
|
||||
if (ephemeral) {
|
||||
d = &decor;
|
||||
} else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines)
|
||||
|| decor.priority != DECOR_PRIORITY_BASE
|
||||
|| decor.hl_eol) {
|
||||
// TODO(bfredl): this is a bit sketchy. eventually we should
|
||||
// have predefined decorations for both marks/ephemerals
|
||||
d = xcalloc(1, sizeof(*d));
|
||||
*d = decor;
|
||||
} else if (decor.hl_id) {
|
||||
d = decor_hl(decor.hl_id);
|
||||
}
|
||||
|
||||
// TODO(bfredl): synergize these two branches even more
|
||||
if (ephemeral && decor_state.buf == buf) {
|
||||
decor_add_ephemeral((int)line, (int)col, line2, col2, &decor);
|
||||
} else {
|
||||
if (ephemeral) {
|
||||
api_set_error(err, kErrorTypeException, "not yet implemented");
|
||||
goto error;
|
||||
}
|
||||
|
||||
extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
|
||||
d, right_gravity, end_right_gravity, kExtmarkNoUndo);
|
||||
|
||||
if (kv_size(decor.virt_lines)) {
|
||||
redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1)));
|
||||
}
|
||||
}
|
||||
|
||||
return (Integer)id;
|
||||
|
||||
error:
|
||||
clear_virttext(&decor.virt_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// (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.
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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 Buffer handle, or 0 for current buffer
|
||||
/// @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) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (line < 0 || line >= MAXLNUM) {
|
||||
api_set_error(err, kErrorTypeValidation, "Line number outside range");
|
||||
return 0;
|
||||
}
|
||||
if (col_start < 0 || col_start > MAXCOL) {
|
||||
api_set_error(err, kErrorTypeValidation, "Column value outside range");
|
||||
return 0;
|
||||
}
|
||||
if (col_end < 0 || col_end > MAXCOL) {
|
||||
col_end = MAXCOL;
|
||||
}
|
||||
|
||||
uint64_t ns = src2ns(&ns_id);
|
||||
|
||||
if (!(line < buf->b_ml.ml_line_count)) {
|
||||
// safety check, we can't add marks outside the range
|
||||
return ns_id;
|
||||
}
|
||||
|
||||
int hl_id = 0;
|
||||
if (hl_group.size > 0) {
|
||||
hl_id = syn_check_group(hl_group.data, (int)hl_group.size);
|
||||
} else {
|
||||
return ns_id;
|
||||
}
|
||||
|
||||
int end_line = (int)line;
|
||||
if (col_end == MAXCOL) {
|
||||
col_end = 0;
|
||||
end_line++;
|
||||
}
|
||||
|
||||
extmark_set(buf, ns, NULL,
|
||||
(int)line, (colnr_T)col_start,
|
||||
end_line, (colnr_T)col_end,
|
||||
decor_hl(hl_id), true, false, kExtmarkNoUndo);
|
||||
return ns_id;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
/// @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 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (line_start < 0 || line_start >= MAXLNUM) {
|
||||
api_set_error(err, kErrorTypeValidation, "Line number outside range");
|
||||
return;
|
||||
}
|
||||
if (line_end < 0 || line_end > MAXLNUM) {
|
||||
line_end = MAXLNUM;
|
||||
}
|
||||
extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
|
||||
(int)line_start, 0,
|
||||
(int)line_end-1, MAXCOL);
|
||||
}
|
||||
|
||||
/// call a function with buffer as temporary current buffer
|
||||
///
|
||||
|
@@ -1,8 +1,6 @@
|
||||
#ifndef NVIM_API_BUFFER_H
|
||||
#define NVIM_API_BUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "nvim/api/buffer.h"
|
||||
#include "nvim/api/deprecated.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/vim.h"
|
||||
|
896
src/nvim/api/extmark.c
Normal file
896
src/nvim/api/extmark.c
Normal file
@@ -0,0 +1,896 @@
|
||||
// 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>
|
||||
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/extmark.h"
|
||||
#include "nvim/lua/executor.h"
|
||||
#include "nvim/memline.h"
|
||||
#include "nvim/screen.h"
|
||||
#include "nvim/syntax.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/extmark.c.generated.h"
|
||||
#endif
|
||||
|
||||
void api_extmark_free_all_mem(void)
|
||||
{
|
||||
String name;
|
||||
handle_T id;
|
||||
map_foreach(&namespace_ids, name, id, {
|
||||
(void)id;
|
||||
xfree(name.data);
|
||||
})
|
||||
map_destroy(String, handle_T)(&namespace_ids);
|
||||
}
|
||||
|
||||
/// 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_extmark()|.
|
||||
///
|
||||
/// 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;
|
||||
}
|
||||
|
||||
const char *describe_ns(NS ns_id)
|
||||
{
|
||||
String name;
|
||||
handle_T id;
|
||||
map_foreach(&namespace_ids, name, id, {
|
||||
if ((NS)id == ns_id && name.size) {
|
||||
return name.data;
|
||||
}
|
||||
})
|
||||
return "(UNKNOWN PLUGIN)";
|
||||
}
|
||||
|
||||
// Is the Namespace in use?
|
||||
static bool ns_initialized(uint64_t ns)
|
||||
{
|
||||
if (ns < 1) {
|
||||
return false;
|
||||
}
|
||||
return ns < (uint64_t)next_namespace_id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
|
||||
{
|
||||
Array rv = ARRAY_DICT_INIT;
|
||||
if (id) {
|
||||
ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id));
|
||||
}
|
||||
ADD(rv, INTEGER_OBJ(extmark.row));
|
||||
ADD(rv, INTEGER_OBJ(extmark.col));
|
||||
|
||||
if (add_dict) {
|
||||
Dictionary dict = ARRAY_DICT_INIT;
|
||||
|
||||
if (extmark.end_row >= 0) {
|
||||
PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
|
||||
PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
|
||||
}
|
||||
|
||||
if (extmark.decor) {
|
||||
Decoration *decor = extmark.decor;
|
||||
if (decor->hl_id) {
|
||||
String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
|
||||
PUT(dict, "hl_group", STRING_OBJ(name));
|
||||
}
|
||||
if (kv_size(decor->virt_text)) {
|
||||
Array chunks = ARRAY_DICT_INIT;
|
||||
for (size_t i = 0; i < decor->virt_text.size; i++) {
|
||||
Array chunk = ARRAY_DICT_INIT;
|
||||
VirtTextChunk *vtc = &decor->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))));
|
||||
}
|
||||
ADD(chunks, ARRAY_OBJ(chunk));
|
||||
}
|
||||
PUT(dict, "virt_text", ARRAY_OBJ(chunks));
|
||||
}
|
||||
|
||||
PUT(dict, "priority", INTEGER_OBJ(decor->priority));
|
||||
}
|
||||
|
||||
if (dict.size) {
|
||||
ADD(rv, DICTIONARY_OBJ(dict));
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
/// Gets the position (0-indexed) of an extmark.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param id Extmark id
|
||||
/// @param opts Optional parameters. Keys:
|
||||
/// - details: Whether to include the details dict
|
||||
/// @param[out] err Error details, if any
|
||||
/// @return 0-indexed (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, 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;
|
||||
}
|
||||
|
||||
bool details = false;
|
||||
for (size_t i = 0; i < opts.size; i++) {
|
||||
String k = opts.items[i].key;
|
||||
Object *v = &opts.items[i].value;
|
||||
if (strequal("details", k.data)) {
|
||||
if (v->type == kObjectTypeBoolean) {
|
||||
details = v->data.boolean;
|
||||
} else if (v->type == kObjectTypeInteger) {
|
||||
details = v->data.integer;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "details is not an boolean");
|
||||
return rv;
|
||||
}
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
|
||||
if (extmark.row < 0) {
|
||||
return rv;
|
||||
}
|
||||
return extmark_to_array(extmark, false, details);
|
||||
}
|
||||
|
||||
/// 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: a 0-indexed (row, col) or valid extmark id
|
||||
/// (whose position defines the bound). |api-indexing|
|
||||
/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
|
||||
/// extmark id (whose position defines the bound). |api-indexing|
|
||||
/// @param opts Optional parameters. Keys:
|
||||
/// - limit: Maximum number of marks to return
|
||||
/// - details Whether to include the details dict
|
||||
/// @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;
|
||||
bool details = false;
|
||||
|
||||
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 if (strequal("details", k.data)) {
|
||||
if (v->type == kObjectTypeBoolean) {
|
||||
details = v->data.boolean;
|
||||
} else if (v->type == kObjectTypeInteger) {
|
||||
details = v->data.integer;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "details is not an boolean");
|
||||
return rv;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
||||
ExtmarkInfoArray 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++) {
|
||||
ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details)));
|
||||
}
|
||||
|
||||
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.)
|
||||
///
|
||||
/// Using the optional arguments, it is possible to use this to highlight
|
||||
/// a range of text, and also to associate virtual text to the mark.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param line Line where to place the mark, 0-based. |api-indexing|
|
||||
/// @param col Column where to place the mark, 0-based. |api-indexing|
|
||||
/// @param opts Optional parameters.
|
||||
/// - id : id of the extmark to edit.
|
||||
/// - end_line : ending line of the mark, 0-based inclusive.
|
||||
/// - end_col : ending col of the mark, 0-based exclusive.
|
||||
/// - hl_group : name of the highlight group used to highlight
|
||||
/// this mark.
|
||||
/// - hl_eol : when true, for a multiline highlight covering the
|
||||
/// EOL of a line, continue the highlight for the rest
|
||||
/// of the screen line (just like for diff and
|
||||
/// cursorline highlight).
|
||||
/// - virt_text : virtual text to link to this mark.
|
||||
/// A list of [text, highlight] tuples, each representing a
|
||||
/// text chunk with specified highlight. `highlight` element
|
||||
/// can either be a a single highlight group, or an array of
|
||||
/// multiple highlight groups that will be stacked
|
||||
/// (highest priority last). A highlight group can be supplied
|
||||
/// either as a string or as an integer, the latter which
|
||||
/// can be obtained using |nvim_get_hl_id_by_name|.
|
||||
/// - virt_text_pos : position of virtual text. Possible values:
|
||||
/// - "eol": right after eol character (default)
|
||||
/// - "overlay": display over the specified column, without
|
||||
/// shifting the underlying text.
|
||||
/// - "right_align": display right aligned in the window.
|
||||
/// - virt_text_win_col : position the virtual text at a fixed
|
||||
/// window column (starting from the first
|
||||
/// text column)
|
||||
/// - virt_text_hide : hide the virtual text when the background
|
||||
/// text is selected or hidden due to
|
||||
/// horizontal scroll 'nowrap'
|
||||
/// - hl_mode : control how highlights are combined with the
|
||||
/// highlights of the text. Currently only affects
|
||||
/// virt_text highlights, but might affect `hl_group`
|
||||
/// in later versions.
|
||||
/// - "replace": only show the virt_text color. This is the
|
||||
/// default
|
||||
/// - "combine": combine with background text color
|
||||
/// - "blend": blend with background text color.
|
||||
///
|
||||
/// - virt_lines : virtual lines to add next to this mark
|
||||
/// This should be an array over lines, where each line in
|
||||
/// turn is an array over [text, highlight] tuples. In
|
||||
/// general, buffer and window options do not affect the
|
||||
/// display of the text. In particular 'wrap'
|
||||
/// and 'linebreak' options do not take effect, so
|
||||
/// the number of extra screen lines will always match
|
||||
/// the size of the array. However the 'tabstop' buffer
|
||||
/// option is still used for hard tabs. By default lines are
|
||||
/// placed below the buffer line containing the mark.
|
||||
///
|
||||
/// - virt_lines_above: place virtual lines above instead.
|
||||
/// - virt_lines_leftcol: Place extmarks in the leftmost
|
||||
/// column of the window, bypassing
|
||||
/// sign and number columns.
|
||||
///
|
||||
/// - ephemeral : for use with |nvim_set_decoration_provider|
|
||||
/// callbacks. The mark will only be used for the current
|
||||
/// redraw cycle, and not be permantently stored in the
|
||||
/// buffer.
|
||||
/// - right_gravity : boolean that indicates the direction
|
||||
/// the extmark will be shifted in when new text is inserted
|
||||
/// (true for right, false for left). defaults to true.
|
||||
/// - end_right_gravity : boolean that indicates the direction
|
||||
/// the extmark end position (if it exists) will be shifted
|
||||
/// in when new text is inserted (true for right, false
|
||||
/// for left). Defaults to false.
|
||||
/// - priority: a priority value for the highlight group. For
|
||||
/// example treesitter highlighting uses a value of 100.
|
||||
/// @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 line, Integer col,
|
||||
Dict(set_extmark) *opts, Error *err)
|
||||
FUNC_API_SINCE(7)
|
||||
{
|
||||
Decoration decor = DECORATION_INIT;
|
||||
|
||||
buf_T *buf = find_buffer_by_handle(buffer, err);
|
||||
if (!buf) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ns_initialized((uint64_t)ns_id)) {
|
||||
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
|
||||
goto error;
|
||||
}
|
||||
|
||||
uint64_t id = 0;
|
||||
if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
|
||||
id = (uint64_t)opts->id.data.integer;
|
||||
} else if (HAS_KEY(opts->id)) {
|
||||
api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
int line2 = -1;
|
||||
if (opts->end_line.type == kObjectTypeInteger) {
|
||||
Integer val = opts->end_line.data.integer;
|
||||
if (val < 0 || val > buf->b_ml.ml_line_count) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_line value outside range");
|
||||
goto error;
|
||||
} else {
|
||||
line2 = (int)val;
|
||||
}
|
||||
} else if (HAS_KEY(opts->end_line)) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_line is not an integer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
colnr_T col2 = -1;
|
||||
if (opts->end_col.type == kObjectTypeInteger) {
|
||||
Integer val = opts->end_col.data.integer;
|
||||
if (val < 0 || val > MAXCOL) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_col value outside range");
|
||||
goto error;
|
||||
} else {
|
||||
col2 = (int)val;
|
||||
}
|
||||
} else if (HAS_KEY(opts->end_col)) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (HAS_KEY(opts->hl_group)) {
|
||||
decor.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->virt_text.type == kObjectTypeArray) {
|
||||
decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
|
||||
&decor.virt_text_width);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (HAS_KEY(opts->virt_text)) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (opts->virt_text_pos.type == kObjectTypeString) {
|
||||
String str = opts->virt_text_pos.data.string;
|
||||
if (strequal("eol", str.data)) {
|
||||
decor.virt_text_pos = kVTEndOfLine;
|
||||
} else if (strequal("overlay", str.data)) {
|
||||
decor.virt_text_pos = kVTOverlay;
|
||||
} else if (strequal("right_align", str.data)) {
|
||||
decor.virt_text_pos = kVTRightAlign;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
|
||||
goto error;
|
||||
}
|
||||
} else if (HAS_KEY(opts->virt_text_pos)) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (opts->virt_text_win_col.type == kObjectTypeInteger) {
|
||||
decor.col = (int)opts->virt_text_win_col.data.integer;
|
||||
decor.virt_text_pos = kVTWinCol;
|
||||
} else if (HAS_KEY(opts->virt_text_win_col)) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"virt_text_win_col is not a Number of the correct size");
|
||||
goto error;
|
||||
}
|
||||
|
||||
#define OPTION_TO_BOOL(target, name, val) \
|
||||
target = api_object_to_bool(opts-> name, #name, val, err); \
|
||||
if (ERROR_SET(err)) { \
|
||||
goto error; \
|
||||
}
|
||||
|
||||
OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
|
||||
OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
|
||||
|
||||
if (opts->hl_mode.type == kObjectTypeString) {
|
||||
String str = opts->hl_mode.data.string;
|
||||
if (strequal("replace", str.data)) {
|
||||
decor.hl_mode = kHlModeReplace;
|
||||
} else if (strequal("combine", str.data)) {
|
||||
decor.hl_mode = kHlModeCombine;
|
||||
} else if (strequal("blend", str.data)) {
|
||||
decor.hl_mode = kHlModeBlend;
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"virt_text_pos: invalid value");
|
||||
goto error;
|
||||
}
|
||||
} else if (HAS_KEY(opts->hl_mode)) {
|
||||
api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool virt_lines_leftcol = false;
|
||||
OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
|
||||
|
||||
if (opts->virt_lines.type == kObjectTypeArray) {
|
||||
Array a = opts->virt_lines.data.array;
|
||||
for (size_t j = 0; j < a.size; j++) {
|
||||
if (a.items[j].type != kObjectTypeArray) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
|
||||
goto error;
|
||||
}
|
||||
int dummig;
|
||||
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
|
||||
kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
} else if (HAS_KEY(opts->virt_lines)) {
|
||||
api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
||||
OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
|
||||
|
||||
if (opts->priority.type == kObjectTypeInteger) {
|
||||
Integer val = opts->priority.data.integer;
|
||||
|
||||
if (val < 0 || val > UINT16_MAX) {
|
||||
api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
|
||||
goto error;
|
||||
}
|
||||
decor.priority = (DecorPriority)val;
|
||||
} else if (HAS_KEY(opts->priority)) {
|
||||
api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool right_gravity = true;
|
||||
OPTION_TO_BOOL(right_gravity, right_gravity, true);
|
||||
|
||||
// Only error out if they try to set end_right_gravity without
|
||||
// setting end_col or end_line
|
||||
if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"cannot set end_right_gravity without setting end_line or end_col");
|
||||
goto error;
|
||||
}
|
||||
|
||||
bool end_right_gravity = false;
|
||||
OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
|
||||
|
||||
size_t len = 0;
|
||||
|
||||
bool ephemeral = false;
|
||||
OPTION_TO_BOOL(ephemeral, ephemeral, false);
|
||||
|
||||
if (line < 0 || line > buf->b_ml.ml_line_count) {
|
||||
api_set_error(err, kErrorTypeValidation, "line value outside range");
|
||||
goto error;
|
||||
} else if (line < buf->b_ml.ml_line_count) {
|
||||
len = ephemeral ? MAXCOL : 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");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (col2 >= 0) {
|
||||
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
|
||||
len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
|
||||
} else if (line2 == buf->b_ml.ml_line_count) {
|
||||
// We are trying to add an extmark past final newline
|
||||
len = 0;
|
||||
} else {
|
||||
// reuse len from before
|
||||
line2 = (int)line;
|
||||
}
|
||||
if (col2 > (Integer)len) {
|
||||
api_set_error(err, kErrorTypeValidation, "end_col value outside range");
|
||||
goto error;
|
||||
}
|
||||
} else if (line2 >= 0) {
|
||||
col2 = 0;
|
||||
}
|
||||
|
||||
Decoration *d = NULL;
|
||||
|
||||
if (ephemeral) {
|
||||
d = &decor;
|
||||
} else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines)
|
||||
|| decor.priority != DECOR_PRIORITY_BASE
|
||||
|| decor.hl_eol) {
|
||||
// TODO(bfredl): this is a bit sketchy. eventually we should
|
||||
// have predefined decorations for both marks/ephemerals
|
||||
d = xcalloc(1, sizeof(*d));
|
||||
*d = decor;
|
||||
} else if (decor.hl_id) {
|
||||
d = decor_hl(decor.hl_id);
|
||||
}
|
||||
|
||||
// TODO(bfredl): synergize these two branches even more
|
||||
if (ephemeral && decor_state.buf == buf) {
|
||||
decor_add_ephemeral((int)line, (int)col, line2, col2, &decor);
|
||||
} else {
|
||||
if (ephemeral) {
|
||||
api_set_error(err, kErrorTypeException, "not yet implemented");
|
||||
goto error;
|
||||
}
|
||||
|
||||
extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
|
||||
d, right_gravity, end_right_gravity, kExtmarkNoUndo);
|
||||
|
||||
if (kv_size(decor.virt_lines)) {
|
||||
redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1)));
|
||||
}
|
||||
}
|
||||
|
||||
return (Integer)id;
|
||||
|
||||
error:
|
||||
clear_virttext(&decor.virt_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
uint64_t src2ns(Integer *src_id)
|
||||
{
|
||||
if (*src_id == 0) {
|
||||
*src_id = nvim_create_namespace((String)STRING_INIT);
|
||||
}
|
||||
if (*src_id < 0) {
|
||||
return UINT64_MAX;
|
||||
} else {
|
||||
return (uint64_t)(*src_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a highlight to buffer.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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`.
|
||||
///
|
||||
/// 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 Buffer handle, or 0 for current buffer
|
||||
/// @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) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (line < 0 || line >= MAXLNUM) {
|
||||
api_set_error(err, kErrorTypeValidation, "Line number outside range");
|
||||
return 0;
|
||||
}
|
||||
if (col_start < 0 || col_start > MAXCOL) {
|
||||
api_set_error(err, kErrorTypeValidation, "Column value outside range");
|
||||
return 0;
|
||||
}
|
||||
if (col_end < 0 || col_end > MAXCOL) {
|
||||
col_end = MAXCOL;
|
||||
}
|
||||
|
||||
uint64_t ns = src2ns(&ns_id);
|
||||
|
||||
if (!(line < buf->b_ml.ml_line_count)) {
|
||||
// safety check, we can't add marks outside the range
|
||||
return ns_id;
|
||||
}
|
||||
|
||||
int hl_id = 0;
|
||||
if (hl_group.size > 0) {
|
||||
hl_id = syn_check_group(hl_group.data, (int)hl_group.size);
|
||||
} else {
|
||||
return ns_id;
|
||||
}
|
||||
|
||||
int end_line = (int)line;
|
||||
if (col_end == MAXCOL) {
|
||||
col_end = 0;
|
||||
end_line++;
|
||||
}
|
||||
|
||||
extmark_set(buf, ns, NULL,
|
||||
(int)line, (colnr_T)col_start,
|
||||
end_line, (colnr_T)col_end,
|
||||
decor_hl(hl_id), true, false, kExtmarkNoUndo);
|
||||
return ns_id;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// @param buffer Buffer handle, or 0 for current buffer
|
||||
/// @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 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (line_start < 0 || line_start >= MAXLNUM) {
|
||||
api_set_error(err, kErrorTypeValidation, "Line number outside range");
|
||||
return;
|
||||
}
|
||||
if (line_end < 0 || line_end > MAXLNUM) {
|
||||
line_end = MAXLNUM;
|
||||
}
|
||||
extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id),
|
||||
(int)line_start, 0,
|
||||
(int)line_end-1, MAXCOL);
|
||||
}
|
||||
|
||||
/// Set or change decoration provider for a namespace
|
||||
///
|
||||
/// This is a very general purpose interface for having lua callbacks
|
||||
/// being triggered during the redraw code.
|
||||
///
|
||||
/// The expected usage is to set extmarks for the currently
|
||||
/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks
|
||||
/// on a per-window or per-lines basis. Use the `ephemeral` key to only
|
||||
/// use the mark for the current screen redraw (the callback will be called
|
||||
/// again for the next redraw ).
|
||||
///
|
||||
/// Note: this function should not be called often. Rather, the callbacks
|
||||
/// themselves can be used to throttle unneeded callbacks. the `on_start`
|
||||
/// callback can return `false` to disable the provider until the next redraw.
|
||||
/// Similarly, return `false` in `on_win` will skip the `on_lines` calls
|
||||
/// for that window (but any extmarks set in `on_win` will still be used).
|
||||
/// A plugin managing multiple sources of decoration should ideally only set
|
||||
/// one provider, and merge the sources internally. You can use multiple `ns_id`
|
||||
/// for the extmarks set/modified inside the callback anyway.
|
||||
///
|
||||
/// Note: doing anything other than setting extmarks is considered experimental.
|
||||
/// Doing things like changing options are not expliticly forbidden, but is
|
||||
/// likely to have unexpected consequences (such as 100% CPU consumption).
|
||||
/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
|
||||
/// for the moment.
|
||||
///
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param opts Callbacks invoked during redraw:
|
||||
/// - on_start: called first on each screen redraw
|
||||
/// ["start", tick]
|
||||
/// - on_buf: called for each buffer being redrawn (before window
|
||||
/// callbacks)
|
||||
/// ["buf", bufnr, tick]
|
||||
/// - on_win: called when starting to redraw a specific window.
|
||||
/// ["win", winid, bufnr, topline, botline_guess]
|
||||
/// - on_line: called for each buffer line being redrawn. (The
|
||||
/// interation with fold lines is subject to change)
|
||||
/// ["win", winid, bufnr, row]
|
||||
/// - on_end: called at the end of a redraw cycle
|
||||
/// ["end", tick]
|
||||
void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err)
|
||||
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
|
||||
{
|
||||
DecorProvider *p = get_decor_provider((NS)ns_id, true);
|
||||
assert(p != NULL);
|
||||
decor_provider_clear(p);
|
||||
|
||||
// regardless of what happens, it seems good idea to redraw
|
||||
redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
LuaRef *dest;
|
||||
} cbs[] = {
|
||||
{ "on_start", &p->redraw_start },
|
||||
{ "on_buf", &p->redraw_buf },
|
||||
{ "on_win", &p->redraw_win },
|
||||
{ "on_line", &p->redraw_line },
|
||||
{ "on_end", &p->redraw_end },
|
||||
{ "_on_hl_def", &p->hl_def },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < opts.size; i++) {
|
||||
String k = opts.items[i].key;
|
||||
Object *v = &opts.items[i].value;
|
||||
size_t j;
|
||||
for (j = 0; cbs[j].name && cbs[j].dest; j++) {
|
||||
if (strequal(cbs[j].name, k.data)) {
|
||||
if (v->type != kObjectTypeLuaRef) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"%s is not a function", cbs[j].name);
|
||||
goto error;
|
||||
}
|
||||
*(cbs[j].dest) = v->data.luaref;
|
||||
v->data.luaref = LUA_NOREF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cbs[j].name) {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
p->active = true;
|
||||
return;
|
||||
error:
|
||||
decor_provider_clear(p);
|
||||
}
|
13
src/nvim/api/extmark.h
Normal file
13
src/nvim/api/extmark.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef NVIM_API_EXTMARK_H
|
||||
#define NVIM_API_EXTMARK_H
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/map.h"
|
||||
|
||||
EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
|
||||
EXTERN handle_T next_namespace_id INIT(= 1);
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/extmark.h.generated.h"
|
||||
#endif
|
||||
#endif // NVIM_API_EXTMARK_H
|
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "nvim/api/buffer.h"
|
||||
#include "nvim/api/deprecated.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/private/dispatch.h"
|
||||
#include "nvim/api/private/helpers.h"
|
||||
|
@@ -1410,15 +1410,6 @@ 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
|
||||
@@ -1607,18 +1598,6 @@ free_exit:
|
||||
return hl_msg;
|
||||
}
|
||||
|
||||
const char *describe_ns(NS ns_id)
|
||||
{
|
||||
String name;
|
||||
handle_T id;
|
||||
map_foreach(&namespace_ids, name, id, {
|
||||
if ((NS)id == ns_id && name.size) {
|
||||
return name.data;
|
||||
}
|
||||
})
|
||||
return "(UNKNOWN PLUGIN)";
|
||||
}
|
||||
|
||||
bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
|
||||
{
|
||||
for (size_t i = 0; i < dict.size; i++) {
|
||||
|
@@ -1,8 +1,6 @@
|
||||
#ifndef NVIM_API_TABPAGE_H
|
||||
#define NVIM_API_TABPAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
|
@@ -59,17 +59,6 @@
|
||||
# include "api/vim.c.generated.h"
|
||||
#endif
|
||||
|
||||
void api_vim_free_all_mem(void)
|
||||
{
|
||||
String name;
|
||||
handle_T id;
|
||||
map_foreach(&namespace_ids, name, id, {
|
||||
(void)id;
|
||||
xfree(name.data);
|
||||
})
|
||||
map_destroy(String, handle_T)(&namespace_ids);
|
||||
}
|
||||
|
||||
/// Executes Vimscript (multiline block of Ex-commands), like anonymous
|
||||
/// |:source|.
|
||||
///
|
||||
@@ -1414,49 +1403,6 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_extmark()|.
|
||||
///
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// Pastes at cursor, in any mode.
|
||||
///
|
||||
/// Invokes the `vim.paste` handler, which handles each mode appropriately.
|
||||
@@ -2745,96 +2691,6 @@ void nvim__screenshot(String path)
|
||||
ui_call_screenshot(path);
|
||||
}
|
||||
|
||||
/// Set or change decoration provider for a namespace
|
||||
///
|
||||
/// This is a very general purpose interface for having lua callbacks
|
||||
/// being triggered during the redraw code.
|
||||
///
|
||||
/// The expected usage is to set extmarks for the currently
|
||||
/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks
|
||||
/// on a per-window or per-lines basis. Use the `ephemeral` key to only
|
||||
/// use the mark for the current screen redraw (the callback will be called
|
||||
/// again for the next redraw ).
|
||||
///
|
||||
/// Note: this function should not be called often. Rather, the callbacks
|
||||
/// themselves can be used to throttle unneeded callbacks. the `on_start`
|
||||
/// callback can return `false` to disable the provider until the next redraw.
|
||||
/// Similarly, return `false` in `on_win` will skip the `on_lines` calls
|
||||
/// for that window (but any extmarks set in `on_win` will still be used).
|
||||
/// A plugin managing multiple sources of decoration should ideally only set
|
||||
/// one provider, and merge the sources internally. You can use multiple `ns_id`
|
||||
/// for the extmarks set/modified inside the callback anyway.
|
||||
///
|
||||
/// Note: doing anything other than setting extmarks is considered experimental.
|
||||
/// Doing things like changing options are not expliticly forbidden, but is
|
||||
/// likely to have unexpected consequences (such as 100% CPU consumption).
|
||||
/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
|
||||
/// for the moment.
|
||||
///
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param opts Callbacks invoked during redraw:
|
||||
/// - on_start: called first on each screen redraw
|
||||
/// ["start", tick]
|
||||
/// - on_buf: called for each buffer being redrawn (before window
|
||||
/// callbacks)
|
||||
/// ["buf", bufnr, tick]
|
||||
/// - on_win: called when starting to redraw a specific window.
|
||||
/// ["win", winid, bufnr, topline, botline_guess]
|
||||
/// - on_line: called for each buffer line being redrawn. (The
|
||||
/// interation with fold lines is subject to change)
|
||||
/// ["win", winid, bufnr, row]
|
||||
/// - on_end: called at the end of a redraw cycle
|
||||
/// ["end", tick]
|
||||
void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err)
|
||||
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
|
||||
{
|
||||
DecorProvider *p = get_decor_provider((NS)ns_id, true);
|
||||
assert(p != NULL);
|
||||
decor_provider_clear(p);
|
||||
|
||||
// regardless of what happens, it seems good idea to redraw
|
||||
redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
LuaRef *dest;
|
||||
} cbs[] = {
|
||||
{ "on_start", &p->redraw_start },
|
||||
{ "on_buf", &p->redraw_buf },
|
||||
{ "on_win", &p->redraw_win },
|
||||
{ "on_line", &p->redraw_line },
|
||||
{ "on_end", &p->redraw_end },
|
||||
{ "_on_hl_def", &p->hl_def },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < opts.size; i++) {
|
||||
String k = opts.items[i].key;
|
||||
Object *v = &opts.items[i].value;
|
||||
size_t j;
|
||||
for (j = 0; cbs[j].name && cbs[j].dest; j++) {
|
||||
if (strequal(cbs[j].name, k.data)) {
|
||||
if (v->type != kObjectTypeLuaRef) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"%s is not a function", cbs[j].name);
|
||||
goto error;
|
||||
}
|
||||
*(cbs[j].dest) = v->data.luaref;
|
||||
v->data.luaref = LUA_NOREF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!cbs[j].name) {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
p->active = true;
|
||||
return;
|
||||
error:
|
||||
decor_provider_clear(p);
|
||||
}
|
||||
|
||||
/// Deletes a uppercase/file named mark. See |mark-motions|.
|
||||
///
|
||||
|
@@ -1,13 +1,7 @@
|
||||
#ifndef NVIM_API_VIM_H
|
||||
#define NVIM_API_VIM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/map.h"
|
||||
|
||||
EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
|
||||
EXTERN handle_T next_namespace_id INIT(= 1);
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "api/vim.h.generated.h"
|
||||
|
@@ -1,8 +1,6 @@
|
||||
#ifndef NVIM_API_WIN_CONFIG_H
|
||||
#define NVIM_API_WIN_CONFIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
|
@@ -1,8 +1,6 @@
|
||||
#ifndef NVIM_API_WINDOW_H
|
||||
#define NVIM_API_WINDOW_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
|
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "nvim/api/buffer.h"
|
||||
#include "nvim/api/private/defs.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/ascii.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_updates.h"
|
||||
|
@@ -30,7 +30,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/buffer.h"
|
||||
#include "nvim/buffer_updates.h"
|
||||
#include "nvim/charset.h"
|
||||
@@ -715,16 +715,3 @@ void extmark_move_region(buf_T *buf, int start_row, colnr_T start_col, bcount_t
|
||||
.data.move = move }));
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t src2ns(Integer *src_id)
|
||||
{
|
||||
if (*src_id == 0) {
|
||||
*src_id = nvim_create_namespace((String)STRING_INIT);
|
||||
}
|
||||
if (*src_id < 0) {
|
||||
return UINT64_MAX;
|
||||
} else {
|
||||
return (uint64_t)(*src_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -81,7 +81,7 @@
|
||||
#ifndef WIN32
|
||||
# include "nvim/os/pty_process_unix.h"
|
||||
#endif
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
|
||||
// values for "window_layout"
|
||||
#define WIN_HOR 1 // "-o" horizontally split windows
|
||||
|
@@ -8,7 +8,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/context.h"
|
||||
#include "nvim/decoration.h"
|
||||
#include "nvim/eval.h"
|
||||
@@ -679,7 +679,7 @@ void free_all_mem(void)
|
||||
}
|
||||
|
||||
eval_clear();
|
||||
api_vim_free_all_mem();
|
||||
api_extmark_free_all_mem();
|
||||
ctx_free_all();
|
||||
|
||||
// Free all buffers. Reset 'autochdir' to avoid accessing things that
|
||||
|
@@ -66,6 +66,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "nvim/api/private/helpers.h"
|
||||
#include "nvim/api/extmark.h"
|
||||
#include "nvim/api/vim.h"
|
||||
#include "nvim/arabic.h"
|
||||
#include "nvim/ascii.h"
|
||||
|
Reference in New Issue
Block a user