mirror of
https://github.com/neovim/neovim.git
synced 2025-09-28 05:58:33 +00:00
Merge pull request #31400 from vanaigr/decor-provider-range
feat(decor): add range-based highlighting
This commit is contained in:
@@ -1004,7 +1004,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
|
||||
/// 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_line` calls
|
||||
/// Similarly, return `false` in `on_win` will skip the `on_line` and `on_range` 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`
|
||||
@@ -1016,7 +1016,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
|
||||
/// Doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
|
||||
/// for the moment.
|
||||
///
|
||||
/// Note: It is not allowed to remove or update extmarks in `on_line` callbacks.
|
||||
/// Note: It is not allowed to remove or update extmarks in `on_line` or `on_range` callbacks.
|
||||
///
|
||||
/// @param ns_id Namespace id from |nvim_create_namespace()|
|
||||
/// @param opts Table of callbacks:
|
||||
@@ -1038,6 +1038,14 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
|
||||
/// ```
|
||||
/// ["line", winid, bufnr, row]
|
||||
/// ```
|
||||
/// - on_range: called for each buffer range being redrawn.
|
||||
/// Range is end-exclusive and may span multiple lines. Range
|
||||
/// bounds point to the first byte of a character. An end position
|
||||
/// of the form (lnum, 0), including (number of lines, 0), is valid
|
||||
/// and indicates that EOL of the preceding line is included.
|
||||
/// ```
|
||||
/// ["range", winid, bufnr, begin_row, begin_col, end_row, end_col]
|
||||
/// ```
|
||||
/// - on_end: called at the end of a redraw cycle
|
||||
/// ```
|
||||
/// ["end", tick]
|
||||
@@ -1061,6 +1069,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
|
||||
{ "on_buf", &opts->on_buf, &p->redraw_buf },
|
||||
{ "on_win", &opts->on_win, &p->redraw_win },
|
||||
{ "on_line", &opts->on_line, &p->redraw_line },
|
||||
{ "on_range", &opts->on_range, &p->redraw_range },
|
||||
{ "on_end", &opts->on_end, &p->redraw_end },
|
||||
{ "_on_hl_def", &opts->_on_hl_def, &p->hl_def },
|
||||
{ "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav },
|
||||
|
@@ -18,6 +18,8 @@ typedef struct {
|
||||
LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow),
|
||||
*Boolean) on_win;
|
||||
LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row), *Boolean) on_line;
|
||||
LuaRefOf(("range" _, Integer winid, Integer bufnr, Integer start_row, Integer start_col,
|
||||
Integer end_row, Integer end_col), *Boolean) on_range;
|
||||
LuaRefOf(("end" _, Integer tick)) on_end;
|
||||
LuaRefOf(("hl_def" _)) _on_hl_def;
|
||||
LuaRefOf(("spell_nav" _)) _on_spell_nav;
|
||||
|
@@ -513,7 +513,7 @@ static void decor_state_pack(DecorState *state)
|
||||
state->future_begin = fut_beg;
|
||||
}
|
||||
|
||||
bool decor_redraw_line(win_T *wp, int row, DecorState *state)
|
||||
void decor_redraw_line(win_T *wp, int row, DecorState *state)
|
||||
{
|
||||
decor_state_pack(state);
|
||||
|
||||
@@ -527,7 +527,11 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
|
||||
state->row = row;
|
||||
state->col_until = -1;
|
||||
state->eol_col = -1;
|
||||
}
|
||||
|
||||
// Checks if there are (likely) more decorations on the current line.
|
||||
bool decor_has_more_decorations(DecorState *state, int row)
|
||||
{
|
||||
if (state->current_end != 0 || state->future_begin != (int)kv_size(state->ranges_i)) {
|
||||
return true;
|
||||
}
|
||||
|
@@ -149,6 +149,7 @@ typedef struct {
|
||||
LuaRef redraw_buf;
|
||||
LuaRef redraw_win;
|
||||
LuaRef redraw_line;
|
||||
LuaRef redraw_range;
|
||||
LuaRef redraw_end;
|
||||
LuaRef hl_def;
|
||||
LuaRef spell_nav;
|
||||
|
@@ -25,7 +25,7 @@ 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, LUA_NOREF, \
|
||||
LUA_NOREF, LUA_NOREF, -1, false, false, 0 }
|
||||
|
||||
static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg)
|
||||
@@ -189,6 +189,30 @@ void decor_providers_invoke_line(win_T *wp, int row)
|
||||
decor_state.running_decor_provider = false;
|
||||
}
|
||||
|
||||
void decor_providers_invoke_range(win_T *wp, int start_row, int start_col, int end_row, int end_col)
|
||||
{
|
||||
decor_state.running_decor_provider = true;
|
||||
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) {
|
||||
MAXSIZE_TEMP_ARRAY(args, 6);
|
||||
ADD_C(args, WINDOW_OBJ(wp->handle));
|
||||
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
|
||||
ADD_C(args, INTEGER_OBJ(start_row));
|
||||
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;
|
||||
}
|
||||
|
||||
hl_check_ns();
|
||||
}
|
||||
}
|
||||
decor_state.running_decor_provider = false;
|
||||
}
|
||||
|
||||
/// For each provider invoke the 'buf' callback for a given buffer.
|
||||
///
|
||||
/// @param buf Buffer
|
||||
@@ -272,6 +296,7 @@ void decor_provider_clear(DecorProvider *p)
|
||||
NLUA_CLEAR_REF(p->redraw_buf);
|
||||
NLUA_CLEAR_REF(p->redraw_win);
|
||||
NLUA_CLEAR_REF(p->redraw_line);
|
||||
NLUA_CLEAR_REF(p->redraw_range);
|
||||
NLUA_CLEAR_REF(p->redraw_end);
|
||||
NLUA_CLEAR_REF(p->spell_nav);
|
||||
NLUA_CLEAR_REF(p->conceal_line);
|
||||
|
@@ -1142,6 +1142,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
// Not drawing text when line is concealed or drawing filler lines beyond last line.
|
||||
const bool draw_text = !concealed && (lnum != buf->b_ml.ml_line_count + 1);
|
||||
|
||||
int decor_provider_end_col;
|
||||
bool check_decor_providers = false;
|
||||
|
||||
if (col_rows == 0 && draw_text) {
|
||||
// To speed up the loop below, set extra_check when there is linebreak,
|
||||
// trailing white space and/or syntax processing to be done.
|
||||
@@ -1163,14 +1166,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
}
|
||||
}
|
||||
|
||||
decor_providers_invoke_line(wp, lnum - 1); // may invalidate wp->w_virtcol
|
||||
validate_virtcol(wp);
|
||||
|
||||
has_decor = decor_redraw_line(wp, lnum - 1, &decor_state);
|
||||
|
||||
if (has_decor) {
|
||||
extra_check = true;
|
||||
}
|
||||
check_decor_providers = true;
|
||||
|
||||
// Check for columns to display for 'colorcolumn'.
|
||||
wlv.color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
|
||||
@@ -1466,22 +1462,22 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
|
||||
// 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
|
||||
// first character to be displayed.
|
||||
const int start_col = wp->w_p_wrap
|
||||
? (startrow == 0 ? wp->w_skipcol : 0)
|
||||
: wp->w_leftcol;
|
||||
const int start_vcol = wp->w_p_wrap
|
||||
? (startrow == 0 ? wp->w_skipcol : 0)
|
||||
: wp->w_leftcol;
|
||||
|
||||
if (has_foldtext) {
|
||||
wlv.vcol = start_col;
|
||||
} else if (start_col > 0 && col_rows == 0) {
|
||||
wlv.vcol = start_vcol;
|
||||
} else if (start_vcol > 0 && col_rows == 0) {
|
||||
char *prev_ptr = ptr;
|
||||
CharSize cs = { 0 };
|
||||
|
||||
CharsizeArg csarg;
|
||||
CSType cstype = init_charsize_arg(&csarg, wp, lnum, line);
|
||||
csarg.max_head_vcol = start_col;
|
||||
csarg.max_head_vcol = start_vcol;
|
||||
int vcol = wlv.vcol;
|
||||
StrCharInfo ci = utf_ptr2StrCharInfo(ptr);
|
||||
while (vcol < start_col && *ci.ptr != NUL) {
|
||||
while (vcol < start_vcol && *ci.ptr != NUL) {
|
||||
cs = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg);
|
||||
vcol += cs.width;
|
||||
prev_ptr = ci.ptr;
|
||||
@@ -1518,23 +1514,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
// - the visual mode is active, or
|
||||
// - drawing a fold
|
||||
// the end of the line may be before the start of the displayed part.
|
||||
if (wlv.vcol < start_col && (wp->w_p_cuc
|
||||
|| wlv.color_cols
|
||||
|| virtual_active(wp)
|
||||
|| (VIsual_active && wp->w_buffer == curwin->w_buffer)
|
||||
|| has_fold)) {
|
||||
wlv.vcol = start_col;
|
||||
if (wlv.vcol < start_vcol && (wp->w_p_cuc
|
||||
|| wlv.color_cols
|
||||
|| virtual_active(wp)
|
||||
|| (VIsual_active && wp->w_buffer == curwin->w_buffer)
|
||||
|| has_fold)) {
|
||||
wlv.vcol = start_vcol;
|
||||
}
|
||||
|
||||
// Handle a character that's not completely on the screen: Put ptr at
|
||||
// that character but skip the first few screen characters.
|
||||
if (wlv.vcol > start_col) {
|
||||
if (wlv.vcol > start_vcol) {
|
||||
wlv.vcol -= charsize;
|
||||
ptr = prev_ptr;
|
||||
}
|
||||
|
||||
if (start_col > wlv.vcol) {
|
||||
wlv.skip_cells = start_col - wlv.vcol - head;
|
||||
if (start_vcol > wlv.vcol) {
|
||||
wlv.skip_cells = start_vcol - wlv.vcol - head;
|
||||
}
|
||||
|
||||
// Adjust for when the inverted text is before the screen,
|
||||
@@ -1588,6 +1584,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
}
|
||||
}
|
||||
|
||||
if (check_decor_providers) {
|
||||
int const col = (int)(ptr - line);
|
||||
decor_provider_end_col = decor_providers_setup(endrow - startrow,
|
||||
start_vcol == 0,
|
||||
lnum,
|
||||
col,
|
||||
wp);
|
||||
line = ml_get_buf(wp->w_buffer, lnum);
|
||||
ptr = line + col;
|
||||
}
|
||||
|
||||
decor_redraw_line(wp, lnum - 1, &decor_state);
|
||||
if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) {
|
||||
has_decor = true;
|
||||
extra_check = true;
|
||||
}
|
||||
|
||||
// Correct highlighting for cursor that can't be disabled.
|
||||
// Avoids having to check this for each character.
|
||||
if (wlv.fromcol >= 0) {
|
||||
@@ -1642,6 +1655,18 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
|
||||
bool did_decrement_ptr = false;
|
||||
|
||||
// Get next chunk of extmark highlights if previous approximation was smaller than needed.
|
||||
if (check_decor_providers && (int)(ptr - line) >= decor_provider_end_col) {
|
||||
int const col = (int)(ptr - line);
|
||||
decor_provider_end_col = invoke_range_next(wp, lnum, col, 100);
|
||||
line = ml_get_buf(wp->w_buffer, lnum);
|
||||
ptr = line + col;
|
||||
if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) {
|
||||
has_decor = true;
|
||||
extra_check = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this quickly when working on the text.
|
||||
if (draw_cols) {
|
||||
if (cul_screenline) {
|
||||
@@ -2740,7 +2765,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
// Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
|
||||
|
||||
// check if line ends before left margin
|
||||
wlv.vcol = MAX(wlv.vcol, start_col + wlv.col - win_col_off(wp));
|
||||
wlv.vcol = MAX(wlv.vcol, start_vcol + wlv.col - win_col_off(wp));
|
||||
// Get rid of the boguscols now, we want to draw until the right
|
||||
// edge for 'cursorcolumn'.
|
||||
wlv.col -= wlv.boguscols;
|
||||
@@ -2762,7 +2787,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
|
||||
|
||||
if (((wp->w_p_cuc
|
||||
&& wp->w_virtcol >= vcol_hlc(wlv) - eol_hl_off
|
||||
&& wp->w_virtcol < view_width * (ptrdiff_t)(wlv.row - startrow + 1) + start_col
|
||||
&& wp->w_virtcol < view_width * (ptrdiff_t)(wlv.row - startrow + 1) + start_vcol
|
||||
&& lnum != wp->w_cursor.lnum)
|
||||
|| wlv.color_cols || wlv.line_attr_lowprio || wlv.line_attr
|
||||
|| wlv.diff_hlf != 0 || wp->w_buffer->terminal)) {
|
||||
@@ -3180,3 +3205,50 @@ static void wlv_put_linebuf(win_T *wp, const winlinevars_T *wlv, int endcol, boo
|
||||
ScreenGrid *g = grid_adjust(grid, &row, &coloff);
|
||||
grid_put_linebuf(g, row, coloff, startcol, endcol, clear_width, bg_attr, 0, wlv->vcol - 1, flags);
|
||||
}
|
||||
|
||||
static int decor_providers_setup(int rows_to_draw, bool draw_from_line_start, linenr_T lnum,
|
||||
colnr_T col, win_T *wp)
|
||||
{
|
||||
// Approximate the number of bytes that will be drawn.
|
||||
// Assume we're dealing with 1-cell ascii and ignore
|
||||
// the effects of 'linebreak', 'breakindent', etc.
|
||||
int rem_vcols;
|
||||
if (wp->w_p_wrap) {
|
||||
int width = wp->w_view_width - win_col_off(wp);
|
||||
int width2 = width + win_col_off2(wp);
|
||||
|
||||
int first_row_width = draw_from_line_start ? width : width2;
|
||||
rem_vcols = first_row_width + (rows_to_draw - 1) * width2;
|
||||
} else {
|
||||
rem_vcols = wp->w_view_height - win_col_off(wp);
|
||||
}
|
||||
|
||||
// Call it here since we need to invalidate the line pointer anyway.
|
||||
decor_providers_invoke_line(wp, lnum - 1);
|
||||
validate_virtcol(wp);
|
||||
|
||||
return invoke_range_next(wp, lnum, col, rem_vcols + 1);
|
||||
}
|
||||
|
||||
/// @return New begin column, or INT_MAX.
|
||||
static int invoke_range_next(win_T *wp, int lnum, colnr_T begin_col, colnr_T col_off)
|
||||
{
|
||||
char const *const line = ml_get_buf(wp->w_buffer, lnum);
|
||||
int const line_len = ml_get_buf_len(wp->w_buffer, lnum);
|
||||
col_off = MAX(col_off, 1);
|
||||
|
||||
colnr_T new_col;
|
||||
if (col_off <= line_len - begin_col) {
|
||||
int end_col = begin_col + col_off;
|
||||
end_col += mb_off_next(line, line + end_col);
|
||||
decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum - 1, end_col);
|
||||
validate_virtcol(wp);
|
||||
new_col = end_col;
|
||||
} else {
|
||||
decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum, 0);
|
||||
validate_virtcol(wp);
|
||||
new_col = INT_MAX;
|
||||
}
|
||||
|
||||
return new_col;
|
||||
}
|
||||
|
@@ -1361,34 +1361,46 @@ static int tslua_push_querycursor(lua_State *L)
|
||||
|
||||
TSQuery *query = query_check(L, 2);
|
||||
TSQueryCursor *cursor = ts_query_cursor_new();
|
||||
|
||||
if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
|
||||
luaL_argcheck(L, lua_istable(L, 3), 3, "table expected");
|
||||
}
|
||||
|
||||
lua_getfield(L, 3, "start_row");
|
||||
uint32_t start_row = (uint32_t)luaL_checkinteger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 3, "start_col");
|
||||
uint32_t start_col = (uint32_t)luaL_checkinteger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 3, "end_row");
|
||||
uint32_t end_row = (uint32_t)luaL_checkinteger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 3, "end_col");
|
||||
uint32_t end_col = (uint32_t)luaL_checkinteger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
ts_query_cursor_set_point_range(cursor, (TSPoint){ start_row, start_col },
|
||||
(TSPoint){ end_row, end_col });
|
||||
|
||||
lua_getfield(L, 3, "max_start_depth");
|
||||
if (!lua_isnil(L, -1)) {
|
||||
uint32_t max_start_depth = (uint32_t)luaL_checkinteger(L, -1);
|
||||
ts_query_cursor_set_max_start_depth(cursor, max_start_depth);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, 3, "match_limit");
|
||||
if (!lua_isnil(L, -1)) {
|
||||
uint32_t match_limit = (uint32_t)luaL_checkinteger(L, -1);
|
||||
ts_query_cursor_set_match_limit(cursor, match_limit);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
ts_query_cursor_exec(cursor, query, node);
|
||||
|
||||
if (lua_gettop(L) >= 3) {
|
||||
uint32_t start = (uint32_t)luaL_checkinteger(L, 3);
|
||||
uint32_t end = lua_gettop(L) >= 4 ? (uint32_t)luaL_checkinteger(L, 4) : MAXLNUM;
|
||||
ts_query_cursor_set_point_range(cursor, (TSPoint){ start, 0 }, (TSPoint){ end, 0 });
|
||||
}
|
||||
|
||||
if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) {
|
||||
luaL_argcheck(L, lua_istable(L, 5), 5, "table expected");
|
||||
lua_pushnil(L); // [dict, ..., nil]
|
||||
while (lua_next(L, 5)) {
|
||||
// [dict, ..., key, value]
|
||||
if (lua_type(L, -2) == LUA_TSTRING) {
|
||||
char *k = (char *)lua_tostring(L, -2);
|
||||
if (strequal("max_start_depth", k)) {
|
||||
uint32_t max_start_depth = (uint32_t)lua_tointeger(L, -1);
|
||||
ts_query_cursor_set_max_start_depth(cursor, max_start_depth);
|
||||
} else if (strequal("match_limit", k)) {
|
||||
uint32_t match_limit = (uint32_t)lua_tointeger(L, -1);
|
||||
ts_query_cursor_set_match_limit(cursor, match_limit);
|
||||
}
|
||||
}
|
||||
// pop the value; lua_next will pop the key.
|
||||
lua_pop(L, 1); // [dict, ..., key]
|
||||
}
|
||||
}
|
||||
|
||||
TSQueryCursor **ud = lua_newuserdata(L, sizeof(*ud)); // [node, query, ..., udata]
|
||||
*ud = cursor;
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_QUERYCURSOR); // [node, query, ..., udata, meta]
|
||||
|
Reference in New Issue
Block a user