Merge pull request #35536 from bfredl/skipahead

perf(highlight): allow decoration providers to skip ranges without data

fixes #35644
This commit is contained in:
bfredl
2025-09-09 20:53:31 +02:00
committed by GitHub
12 changed files with 161 additions and 114 deletions

View File

@@ -836,6 +836,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
col2 = c;
}
DecorPriority subpriority = 0;
if (HAS_KEY(opts, set_extmark, _subpriority)) {
VALIDATE_RANGE((opts->_subpriority >= 0 && opts->_subpriority <= UINT16_MAX),
"_subpriority", {
goto error;
});
subpriority = (DecorPriority)opts->_subpriority;
}
if (kv_size(virt_text.data.virt_text)) {
decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true);
}
@@ -845,7 +854,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (has_hl) {
DecorSignHighlight sh = decor_sh_from_inline(hl);
sh.url = url;
decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id,
subpriority);
}
} else {
if (opts->ephemeral) {
@@ -1051,6 +1061,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// ```
/// ["range", winid, bufnr, begin_row, begin_col, end_row, end_col]
/// ```
///
/// In addition to returning a boolean, it is also allowed to
/// return a `skip_row, skip_col` pair of integers. This implies
/// that this function does not need to be called until a range
/// which continues beyond the skipped position. A single integer
/// return value `skip_row` is short for `skip_row, 0`
///
/// - on_end: called at the end of a redraw cycle
/// ```
/// ["end", tick]

View File

@@ -62,6 +62,8 @@ typedef struct {
Boolean undo_restore;
String url;
Boolean scoped;
Integer _subpriority;
} Dict(set_extmark);
typedef struct {

View File

@@ -553,12 +553,12 @@ static void decor_range_add_from_inline(DecorState *state, int start_row, int st
uint32_t idx = decor.data.ext.sh_idx;
while (idx != DECOR_ID_INVALID) {
DecorSignHighlight *sh = &kv_A(decor_items, idx);
decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id);
decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id, 0);
idx = sh->next;
}
} else {
DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl);
decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id);
decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id, 0);
}
}
@@ -619,14 +619,15 @@ void decor_range_add_virt(DecorState *state, int start_row, int start_col, int e
.data.vt = vt,
.attr_id = 0,
.owned = owned,
.priority = vt->priority,
.priority_internal = ((DecorPriorityInternal)vt->priority << 16),
.draw_col = -10,
};
decor_range_insert(state, &range);
}
void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col,
DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id)
DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id,
DecorPriority subpriority)
{
if (sh->flags & kSHIsSign) {
return;
@@ -638,7 +639,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end
.data.sh = *sh,
.attr_id = 0,
.owned = owned,
.priority = sh->priority,
.priority_internal = ((DecorPriorityInternal)sh->priority << 16) + subpriority,
.draw_col = -10,
};
@@ -731,7 +732,7 @@ next_mark:
break;
}
int const ordering = r->ordering;
DecorPriority const priority = r->priority;
DecorPriorityInternal const priority = r->priority_internal;
int begin = 0;
int end = cur_end;
@@ -739,7 +740,8 @@ next_mark:
int mid = begin + ((end - begin) >> 1);
int mi = indices[mid];
DecorRange *mr = &slots[mi].range;
if (mr->priority < priority || (mr->priority == priority && mr->ordering < ordering)) {
if (mr->priority_internal < priority
|| (mr->priority_internal == priority && mr->ordering < ordering)) {
begin = mid + 1;
} else {
end = mid;

View File

@@ -37,7 +37,7 @@ typedef struct {
int end_row;
int end_col;
int ordering; ///< range insertion order
DecorPriority priority;
DecorPriorityInternal priority_internal;
bool owned; ///< ephemeral decoration, free memory immediately
DecorRangeKind kind;
// next pointers MUST NOT be used, these are separate ranges

View File

@@ -36,6 +36,7 @@ enum {
typedef kvec_t(struct virt_line { VirtText line; int flags; }) VirtLines;
typedef uint16_t DecorPriority;
typedef uint32_t DecorPriorityInternal;
#define DECOR_PRIORITY_BASE 0x1000
/// Keep in sync with hl_mode_str[] in decoration.h
@@ -145,6 +146,9 @@ typedef struct {
kDecorProviderDisabled = 4,
} state;
int win_skip_row;
int win_skip_col;
LuaRef redraw_start;
LuaRef redraw_buf;
LuaRef redraw_win;
@@ -159,3 +163,8 @@ typedef struct {
uint8_t error_count;
} DecorProvider;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, kDecorProviderDisabled, 0, 0, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, -1, false, false, 0 }

View File

@@ -23,11 +23,6 @@
static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, kDecorProviderDisabled, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, -1, false, false, 0 }
static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg)
{
const char *ns = describe_ns(provider->ns_id, "(UNKNOWN PLUGIN)");
@@ -38,21 +33,28 @@ static void decor_provider_error(DecorProvider *provider, const char *name, cons
// Note we pass in a provider index as this function may cause decor_providers providers to be
// reallocated so we need to be careful with DecorProvider pointers
static bool decor_provider_invoke(int provider_idx, const char *name, LuaRef ref, Array args,
bool default_true)
bool default_true, Array *res)
{
Error err = ERROR_INIT;
textlock++;
Object ret = nlua_call_ref(ref, name, args, kRetNilBool, NULL, &err);
Object ret = nlua_call_ref(ref, name, args, res ? kRetMulti : kRetNilBool, NULL, &err);
textlock--;
// We get the provider here via an index in case the above call to nlua_call_ref causes
// decor_providers to be reallocated.
DecorProvider *provider = &kv_A(decor_providers, provider_idx);
if (!ERROR_SET(&err)
&& api_object_to_bool(ret, "provider %s retval", default_true, &err)) {
if (!ERROR_SET(&err)) {
provider->error_count = 0;
return true;
if (res) {
assert(ret.type == kObjectTypeArray);
*res = ret.data.array;
return true;
} else {
if (api_object_to_bool(ret, "provider %s retval", default_true, &err)) {
return true;
}
}
}
if (ERROR_SET(&err) && provider->error_count < CB_MAX_ERROR) {
@@ -65,7 +67,7 @@ static bool decor_provider_invoke(int provider_idx, const char *name, LuaRef ref
}
api_clear_error(&err);
api_free_object(ret);
api_free_object(ret); // TODO(bfredl): wants to be on an arena
return false;
}
@@ -81,7 +83,7 @@ void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int e
ADD_C(args, INTEGER_OBJ(start_col));
ADD_C(args, INTEGER_OBJ(end_row));
ADD_C(args, INTEGER_OBJ(end_col));
decor_provider_invoke((int)i, "spell", p->spell_nav, args, true);
decor_provider_invoke((int)i, "spell", p->spell_nav, args, true, NULL);
}
}
}
@@ -97,7 +99,7 @@ bool decor_providers_invoke_conceal_line(win_T *wp, int row)
ADD_C(args, INTEGER_OBJ(wp->handle));
ADD_C(args, INTEGER_OBJ(wp->w_buffer->handle));
ADD_C(args, INTEGER_OBJ(row));
decor_provider_invoke((int)i, "conceal_line", p->conceal_line, args, true);
decor_provider_invoke((int)i, "conceal_line", p->conceal_line, args, true, NULL);
}
}
return wp->w_buffer->b_marktree->n_keys > keys;
@@ -114,7 +116,7 @@ void decor_providers_start(void)
if (p->state != kDecorProviderDisabled && p->redraw_start != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, INTEGER_OBJ((int)display_tick));
bool active = decor_provider_invoke((int)i, "start", p->redraw_start, args, true);
bool active = decor_provider_invoke((int)i, "start", p->redraw_start, args, true, NULL);
kv_A(decor_providers, i).state = active ? kDecorProviderActive : kDecorProviderRedrawDisabled;
} else if (p->state != kDecorProviderDisabled) {
kv_A(decor_providers, i).state = kDecorProviderActive;
@@ -147,6 +149,9 @@ void decor_providers_invoke_win(win_T *wp)
p->state = kDecorProviderActive;
}
p->win_skip_row = 0;
p->win_skip_col = 0;
if (p->state == kDecorProviderActive && p->redraw_win != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 4);
ADD_C(args, WINDOW_OBJ(wp->handle));
@@ -154,7 +159,8 @@ void decor_providers_invoke_win(win_T *wp)
// TODO(bfredl): we are not using this, but should be first drawn line?
ADD_C(args, INTEGER_OBJ(wp->w_topline - 1));
ADD_C(args, INTEGER_OBJ(botline - 1));
if (!decor_provider_invoke((int)i, "win", p->redraw_win, args, true)) {
// TODO(bfredl): could skip a call if retval was interpreted like range?
if (!decor_provider_invoke((int)i, "win", p->redraw_win, args, true, NULL)) {
kv_A(decor_providers, i).state = kDecorProviderWinDisabled;
}
}
@@ -178,7 +184,7 @@ void decor_providers_invoke_line(win_T *wp, int row)
ADD_C(args, WINDOW_OBJ(wp->handle));
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
ADD_C(args, INTEGER_OBJ(row));
if (!decor_provider_invoke((int)i, "line", p->redraw_line, args, true)) {
if (!decor_provider_invoke((int)i, "line", p->redraw_line, args, true, NULL)) {
// return 'false' or error: skip rest of this window
kv_A(decor_providers, i).state = kDecorProviderWinDisabled;
}
@@ -195,6 +201,10 @@ void decor_providers_invoke_range(win_T *wp, int start_row, int start_col, int e
for (size_t i = 0; i < kv_size(decor_providers); i++) {
DecorProvider *p = &kv_A(decor_providers, i);
if (p->state == kDecorProviderActive && p->redraw_range != LUA_NOREF) {
if (p->win_skip_row > end_row || (p->win_skip_row == end_row && p->win_skip_col >= end_col)) {
continue;
}
MAXSIZE_TEMP_ARRAY(args, 6);
ADD_C(args, WINDOW_OBJ(wp->handle));
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
@@ -202,11 +212,35 @@ void decor_providers_invoke_range(win_T *wp, int start_row, int start_col, int e
ADD_C(args, INTEGER_OBJ(start_col));
ADD_C(args, INTEGER_OBJ(end_row));
ADD_C(args, INTEGER_OBJ(end_col));
if (!decor_provider_invoke((int)i, "range", p->redraw_range, args, true)) {
// return 'false' or error: skip rest of this window
kv_A(decor_providers, i).state = kDecorProviderWinDisabled;
Array res = ARRAY_DICT_INIT;
bool status = decor_provider_invoke((int)i, "range", p->redraw_range, args, true, &res);
p = &kv_A(decor_providers, i); // lua call might have reallocated decor_providers
if (!status) {
// error: skip rest of this window
p->state = kDecorProviderWinDisabled;
} else if (res.size >= 1) {
Object first = res.items[0];
if (first.type == kObjectTypeBoolean) {
if (first.data.boolean == false) {
p->state = kDecorProviderWinDisabled;
}
} else if (first.type == kObjectTypeInteger) {
Integer row = first.data.integer;
Integer col = 0;
if (res.size >= 2) {
Object second = res.items[1];
if (second.type == kObjectTypeInteger) {
col = second.data.integer;
}
}
p->win_skip_row = (int)row;
p->win_skip_col = (int)col;
}
}
api_free_array(res);
hl_check_ns();
}
}
@@ -226,7 +260,7 @@ void decor_providers_invoke_buf(buf_T *buf)
MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, BUFFER_OBJ(buf->handle));
ADD_C(args, INTEGER_OBJ((int64_t)display_tick));
decor_provider_invoke((int)i, "buf", p->redraw_buf, args, true);
decor_provider_invoke((int)i, "buf", p->redraw_buf, args, true, NULL);
}
}
}
@@ -243,7 +277,7 @@ void decor_providers_invoke_end(void)
if (p->state != kDecorProviderDisabled && p->redraw_end != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, INTEGER_OBJ((int)display_tick));
decor_provider_invoke((int)i, "end", p->redraw_end, args, true);
decor_provider_invoke((int)i, "end", p->redraw_end, args, true, NULL);
}
}
decor_check_to_be_deleted();

View File

@@ -181,12 +181,9 @@ int nlua_pcall(lua_State *lstate, int nargs, int nresults)
lua_remove(lstate, -2);
} else {
if (nresults == LUA_MULTRET) {
int new_top = lua_gettop(lstate);
int actual_nres = new_top - pre_top + nargs + 1;
lua_remove(lstate, -1 - actual_nres);
} else {
lua_remove(lstate, -1 - nresults);
nresults = lua_gettop(lstate) - (pre_top - nargs - 1);
}
lua_remove(lstate, -1 - nresults);
}
return status;
}
@@ -1551,6 +1548,7 @@ Object nlua_exec(const String str, const char *chunkname, const Array args, LuaR
{
lua_State *const lstate = global_lstate;
int top = lua_gettop(lstate);
const char *name = (chunkname && chunkname[0]) ? chunkname : "<nvim>";
if (luaL_loadbuffer(lstate, str.data, str.size, name)) {
size_t len;
@@ -1570,7 +1568,7 @@ Object nlua_exec(const String str, const char *chunkname, const Array args, LuaR
return NIL;
}
return nlua_call_pop_retval(lstate, mode, arena, err);
return nlua_call_pop_retval(lstate, mode, arena, top, err);
}
bool nlua_ref_is_function(LuaRef ref)
@@ -1601,10 +1599,16 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, LuaRetMode mode,
return nlua_call_ref_ctx(false, ref, name, args, mode, arena, err);
}
static int mode_ret(LuaRetMode mode)
{
return mode == kRetMulti ? LUA_MULTRET : 1;
}
Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, LuaRetMode mode,
Arena *arena, Error *err)
{
lua_State *const lstate = global_lstate;
int top = lua_gettop(lstate);
nlua_pushref(lstate, ref);
int nargs = (int)args.size;
if (name != NULL) {
@@ -1616,12 +1620,12 @@ Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, Lu
}
if (fast) {
if (nlua_fast_cfpcall(lstate, nargs, 1, -1) < 0) {
if (nlua_fast_cfpcall(lstate, nargs, mode_ret(mode), -1) < 0) {
// error is already scheduled, set anyways to convey failure.
api_set_error(err, kErrorTypeException, "fast context failure");
return NIL;
}
} else if (nlua_pcall(lstate, nargs, 1)) {
} else if (nlua_pcall(lstate, nargs, mode_ret(mode))) {
// if err is passed, the caller will deal with the error.
if (err) {
size_t len;
@@ -1633,16 +1637,18 @@ Object nlua_call_ref_ctx(bool fast, LuaRef ref, const char *name, Array args, Lu
return NIL;
}
return nlua_call_pop_retval(lstate, mode, arena, err);
return nlua_call_pop_retval(lstate, mode, arena, top, err);
}
static Object nlua_call_pop_retval(lua_State *lstate, LuaRetMode mode, Arena *arena, Error *err)
static Object nlua_call_pop_retval(lua_State *lstate, LuaRetMode mode, Arena *arena, int pretop,
Error *err)
{
if (lua_isnil(lstate, -1)) {
if (mode != kRetMulti && lua_isnil(lstate, -1)) {
lua_pop(lstate, 1);
return NIL;
}
Error dummy = ERROR_INIT;
Error *perr = err ? err : &dummy;
switch (mode) {
case kRetNilBool: {
@@ -1658,7 +1664,19 @@ static Object nlua_call_pop_retval(lua_State *lstate, LuaRetMode mode, Arena *ar
return LUAREF_OBJ(ref);
}
case kRetObject:
return nlua_pop_Object(lstate, false, arena, err ? err : &dummy);
return nlua_pop_Object(lstate, false, arena, perr);
case kRetMulti:
;
int nres = lua_gettop(lstate) - pretop;
Array res = arena_array(arena, (size_t)nres);
for (int i = 0; i < nres; i++) {
res.items[nres - i - 1] = nlua_pop_Object(lstate, false, arena, perr);
if (ERROR_SET(err)) {
return NIL;
}
}
res.size = (size_t)nres;
return ARRAY_OBJ(res);
}
UNREACHABLE;
}

View File

@@ -38,10 +38,11 @@ typedef struct {
} while (0)
typedef enum {
kRetObject, ///< any object, but doesn't preserve nested luarefs
kRetObject, ///< any object, but doesn't preserve nested luarefs
kRetNilBool, ///< NIL preserved as such, other values return their booleanness
///< Should also be used when return value is ignored, as it is allocation-free
kRetLuaref, ///< return value becomes a single Luaref, regardless of type (except NIL)
kRetLuaref, ///< return value becomes a single Luaref, regardless of type (except NIL)
kRetMulti, ///< like kRetObject but return muliple return values as an Array
} LuaRetMode;
/// Maximum number of errors in vim.ui_attach() and decor provider callbacks.