mirror of
https://github.com/neovim/neovim.git
synced 2025-10-06 01:46:29 +00:00
api: multiple decoration providers at once
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <lauxlib.h>
|
||||
|
||||
#include "nvim/api/buffer.h"
|
||||
@@ -1285,6 +1286,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
|
||||
/// - hl_group : name of the highlight group used to highlight
|
||||
/// this mark.
|
||||
/// - virt_text : virtual text to link to this mark.
|
||||
/// - 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.
|
||||
/// @param[out] err Error details, if any
|
||||
/// @return Id of the created/updated extmark
|
||||
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
|
||||
@@ -1318,6 +1323,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ephemeral = false;
|
||||
|
||||
uint64_t id = 0;
|
||||
int line2 = -1, hl_id = 0;
|
||||
colnr_T col2 = 0;
|
||||
@@ -1386,6 +1393,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
} else if (strequal("ephemeral", k.data)) {
|
||||
ephemeral = api_is_truthy(*v, "ephemeral", false, err);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
goto error;
|
||||
@@ -1410,17 +1422,33 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
|
||||
col2 = 0;
|
||||
}
|
||||
|
||||
Decoration *decor = NULL;
|
||||
if (kv_size(virt_text)) {
|
||||
decor = xcalloc(1, sizeof(*decor));
|
||||
decor->hl_id = hl_id;
|
||||
decor->virt_text = virt_text;
|
||||
} else if (hl_id) {
|
||||
decor = decoration_hl(hl_id);
|
||||
}
|
||||
// TODO(bfredl): synergize these two branches even more
|
||||
if (ephemeral && redrawn_win && redrawn_win->w_buffer == buf) {
|
||||
int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0;
|
||||
VirtText *vt_allocated = NULL;
|
||||
if (kv_size(virt_text)) {
|
||||
vt_allocated = xmalloc(sizeof *vt_allocated);
|
||||
*vt_allocated = virt_text;
|
||||
}
|
||||
decorations_add_ephemeral(attr_id, (int)line, (colnr_T)col,
|
||||
(int)line2, (colnr_T)col2, vt_allocated);
|
||||
} else {
|
||||
if (ephemeral) {
|
||||
api_set_error(err, kErrorTypeException, "not yet implemented");
|
||||
goto error;
|
||||
}
|
||||
Decoration *decor = NULL;
|
||||
if (kv_size(virt_text)) {
|
||||
decor = xcalloc(1, sizeof(*decor));
|
||||
decor->hl_id = hl_id;
|
||||
decor->virt_text = virt_text;
|
||||
} else if (hl_id) {
|
||||
decor = decoration_hl(hl_id);
|
||||
}
|
||||
|
||||
id = extmark_set(buf, (uint64_t)ns_id, id,
|
||||
(int)line, (colnr_T)col, line2, col2, decor, kExtmarkNoUndo);
|
||||
id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col,
|
||||
line2, col2, decor, kExtmarkNoUndo);
|
||||
}
|
||||
|
||||
return (Integer)id;
|
||||
|
||||
|
@@ -1619,14 +1619,28 @@ free_exit:
|
||||
return virt_text;
|
||||
}
|
||||
|
||||
bool api_is_truthy(Object obj, const char *what, Error *err)
|
||||
bool api_is_truthy(Object obj, const char *what, bool nil_truthy, Error *err)
|
||||
{
|
||||
if (obj.type == kObjectTypeBoolean) {
|
||||
return obj.data.boolean;
|
||||
} else if (obj.type == kObjectTypeInteger) {
|
||||
return obj.data.integer; // C semantics: non-zery int is true
|
||||
return obj.data.integer; // C semantics: non-zero int is true
|
||||
} else if (obj.type == kObjectTypeNil) {
|
||||
return nil_truthy; // caller decides what NIL (missing retval in lua) means
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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)";
|
||||
}
|
||||
|
@@ -2604,166 +2604,134 @@ 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 line, Integer col, Dictionary opts, Error *err)
|
||||
FUNC_API_LUA_ONLY
|
||||
{
|
||||
if (!lua_attr_active) {
|
||||
return;
|
||||
}
|
||||
int line2 = -1, hl_id = 0;
|
||||
colnr_T col2 = 0;
|
||||
VirtText virt_text = KV_INITIAL_VALUE;
|
||||
for (size_t i = 0; i < opts.size; i++) {
|
||||
String k = opts.items[i].key;
|
||||
Object *v = &opts.items[i].value;
|
||||
if (strequal("end_line", k.data)) {
|
||||
if (v->type != kObjectTypeInteger) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"end_line is not an integer");
|
||||
goto error;
|
||||
}
|
||||
if (v->data.integer < 0) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"end_line value outside range");
|
||||
goto error;
|
||||
}
|
||||
|
||||
line2 = (int)v->data.integer;
|
||||
} else if (strequal("end_col", k.data)) {
|
||||
if (v->type != kObjectTypeInteger) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"end_col is not an integer");
|
||||
goto error;
|
||||
}
|
||||
if (v->data.integer < 0 || v->data.integer > MAXCOL) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"end_col value outside range");
|
||||
goto error;
|
||||
}
|
||||
|
||||
col2 = (colnr_T)v->data.integer;
|
||||
} else if (strequal("hl_group", k.data)) {
|
||||
String hl_group;
|
||||
switch (v->type) {
|
||||
case kObjectTypeString:
|
||||
hl_group = v->data.string;
|
||||
hl_id = syn_check_group(
|
||||
(char_u *)(hl_group.data),
|
||||
(int)hl_group.size);
|
||||
break;
|
||||
case kObjectTypeInteger:
|
||||
hl_id = (int)v->data.integer;
|
||||
break;
|
||||
default:
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"hl_group is not valid.");
|
||||
goto error;
|
||||
}
|
||||
} else if (strequal("virt_text", k.data)) {
|
||||
if (v->type != kObjectTypeArray) {
|
||||
api_set_error(err, kErrorTypeValidation,
|
||||
"virt_text is not an Array");
|
||||
goto error;
|
||||
}
|
||||
virt_text = parse_virt_text(v->data.array, err);
|
||||
if (ERROR_SET(err)) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (col2 && line2 < 0) {
|
||||
line2 = (int)line;
|
||||
}
|
||||
|
||||
int attr = hl_id ? syn_id2attr((int)hl_id) : 0;
|
||||
if (attr == 0 && !kv_size(virt_text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VirtText *v = xmalloc(sizeof(*v));
|
||||
*v = virt_text; // LeakSanitizer be sad
|
||||
decorations_add_luahl_attr(attr, (int)line, (colnr_T)col,
|
||||
(int)line2, (colnr_T)col2, v);
|
||||
error:
|
||||
return;
|
||||
}
|
||||
|
||||
void nvim__screenshot(String path)
|
||||
FUNC_API_FAST
|
||||
{
|
||||
ui_call_screenshot(path);
|
||||
}
|
||||
|
||||
static void clear_luahl(bool force)
|
||||
static DecorationProvider *get_provider(NS ns_id, bool force)
|
||||
{
|
||||
if (luahl_active || force) {
|
||||
api_free_luaref(luahl_start);
|
||||
api_free_luaref(luahl_win);
|
||||
api_free_luaref(luahl_line);
|
||||
api_free_luaref(luahl_end);
|
||||
ssize_t i;
|
||||
for (i = 0; i < (ssize_t)kv_size(decoration_providers); i++) {
|
||||
DecorationProvider *item = &kv_A(decoration_providers, i);
|
||||
if (item->ns_id == ns_id) {
|
||||
return item;
|
||||
} else if (item->ns_id > ns_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
luahl_start = LUA_NOREF;
|
||||
luahl_win = LUA_NOREF;
|
||||
luahl_line = LUA_NOREF;
|
||||
luahl_end = LUA_NOREF;
|
||||
luahl_active = false;
|
||||
|
||||
if (!force) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (ssize_t j = (ssize_t)kv_size(decoration_providers)-1; j >= i; j++) {
|
||||
// allocates if needed:
|
||||
kv_a(decoration_providers, (size_t)j+1) = kv_A(decoration_providers, j);
|
||||
}
|
||||
DecorationProvider *item = &kv_a(decoration_providers, (size_t)i);
|
||||
*item = DECORATION_PROVIDER_INIT(ns_id);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/// 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__set_luahl(DictionaryOf(LuaRef) opts, Error *err)
|
||||
FUNC_API_LUA_ONLY
|
||||
static void clear_provider(DecorationProvider *p)
|
||||
{
|
||||
redraw_later(NOT_VALID);
|
||||
clear_luahl(false);
|
||||
NLUA_CLEAR_REF(p->redraw_start);
|
||||
NLUA_CLEAR_REF(p->redraw_buf);
|
||||
NLUA_CLEAR_REF(p->redraw_win);
|
||||
NLUA_CLEAR_REF(p->redraw_line);
|
||||
NLUA_CLEAR_REF(p->redraw_end);
|
||||
p->active = false;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Similarily, 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 decorations 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
|
||||
{
|
||||
DecorationProvider *p = get_provider((NS)ns_id, true);
|
||||
clear_provider(p);
|
||||
|
||||
// regardless of what happens, it seems good idea to redraw
|
||||
redraw_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 },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
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;
|
||||
size_t j;
|
||||
for (j = 0; cbs[j].name; 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;
|
||||
}
|
||||
luahl_start = v->data.luaref;
|
||||
v->data.luaref = LUA_NOREF;
|
||||
} else if (strequal("on_win", k.data)) {
|
||||
if (v->type != kObjectTypeLuaRef) {
|
||||
api_set_error(err, kErrorTypeValidation, "callback is not a function");
|
||||
goto error;
|
||||
}
|
||||
luahl_win = 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;
|
||||
}
|
||||
luahl_line = v->data.luaref;
|
||||
v->data.luaref = LUA_NOREF;
|
||||
} else {
|
||||
}
|
||||
if (!cbs[j].name) {
|
||||
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
luahl_active = true;
|
||||
|
||||
p->active = true;
|
||||
return;
|
||||
error:
|
||||
clear_luahl(true);
|
||||
clear_provider(p);
|
||||
}
|
||||
|
Reference in New Issue
Block a user