Skip to content

Commit

Permalink
perf(treesitter): use child_containing_descendant() in has-ancestor? (n…
Browse files Browse the repository at this point in the history
…eovim#28512)

Problem: `has-ancestor?` is O(n²) for the depth of the tree since it iterates over each of the node's ancestors (bottom-up), and each ancestor takes O(n) time.
This happens because tree-sitter's nodes don't store their parent nodes, and the tree is searched (top-down) each time a new parent is requested.

Solution: Make use of new `ts_node_child_containing_descendant()` in tree-sitter v0.22.6 (which is now the minimum required version) to rewrite the `has-ancestor?` predicate in C to become O(n).

For a sample file, decreases the time taken by `has-ancestor?` from 360ms to 6ms.
  • Loading branch information
vanaigr authored and altermo committed May 16, 2024
1 parent 31dc627 commit 7d963d4
Show file tree
Hide file tree
Showing 23 changed files with 338 additions and 186 deletions.
23 changes: 21 additions & 2 deletions runtime/doc/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2776,8 +2776,6 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
• url: A URL to associate with this extmark. In the TUI, the
OSC 8 control sequence is used to generate a clickable
hyperlink to this URL.
• scoped: boolean (EXPERIMENTAL) enables "scoping" for the
extmark. See |nvim__win_add_ns()|

Return: ~
Id of the created/updated extmark
Expand Down Expand Up @@ -2859,6 +2857,27 @@ nvim_set_decoration_provider({ns_id}, {opts})
["end", tick]
<

nvim__ns_get({ns_id}) *nvim__ns_get()*
EXPERIMENTAL: this API will change in the future.

Get the properties for namespace

Parameters: ~
• {ns_id} Namespace

Return: ~
Map defining the namespace properties, see |nvim__ns_set()|

nvim__ns_set({ns_id}, {opts}) *nvim__ns_set()*
EXPERIMENTAL: this API will change in the future.

Set some properties for namespace

Parameters: ~
• {ns_id} Namespace
{opts} Optional parameters to set:
• scoped: (boolean) Make the namespace a scoped namespace

nvim__win_add_ns({window}, {ns_id}) *nvim__win_add_ns()*
EXPERIMENTAL: this API will change in the future.

Expand Down
3 changes: 2 additions & 1 deletion runtime/doc/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ The following new features were added.

API

• TODO
|nvim__ns_set()| can set properties for a namespace
• Has the option `scoped` which is needed for window-local scope to work.

DEFAULTS

Expand Down
5 changes: 5 additions & 0 deletions runtime/doc/treesitter.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ An instance `TSNode` of a treesitter node supports the following methods.

TSNode:parent() *TSNode:parent()*
Get the node's immediate parent.
Prefer |TSNode:child_containing_descendant()|
for iterating over the node's ancestors.

TSNode:next_sibling() *TSNode:next_sibling()*
Get the node's next sibling.
Expand Down Expand Up @@ -114,6 +116,9 @@ TSNode:named_child({index}) *TSNode:named_child()*
Get the node's named child at the given {index}, where zero represents the
first named child.

TSNode:child_containing_descendant({descendant}) *TSNode:child_containing_descendant()*
Get the node's child that contains {descendant}.

TSNode:start() *TSNode:start()*
Get the node's start position. Return three values: the row, column and
total byte count (all zero-based).
Expand Down
21 changes: 19 additions & 2 deletions runtime/lua/vim/_meta/api.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions runtime/lua/vim/_meta/api_keysets.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions runtime/lua/vim/highlight.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ M.priorities = {
--- Indicates priority of highlight
--- (default: `vim.highlight.priorities.user`)
--- @field priority? integer
---
--- @field package _scoped? boolean

--- Apply highlight group to range of text.
---
Expand All @@ -47,7 +45,6 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
local regtype = opts.regtype or 'v'
local inclusive = opts.inclusive or false
local priority = opts.priority or M.priorities.user
local scoped = opts._scoped or false

-- TODO: in case of 'v', 'V' (not block), this should calculate equivalent
-- bounds (row, col, end_row, end_col) as multiline regions are natively
Expand All @@ -65,12 +62,12 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
end_col = cols[2],
priority = priority,
strict = false,
scoped = scoped,
})
end
end

local yank_ns = api.nvim_create_namespace('hlyank')
vim.api.nvim__ns_set(yank_ns, { scoped = true })
local yank_timer --- @type uv.uv_timer_t?
local yank_cancel --- @type fun()?

Expand Down Expand Up @@ -134,7 +131,6 @@ function M.on_yank(opts)
regtype = event.regtype,
inclusive = event.inclusive,
priority = opts.priority or M.priorities.user,
_scoped = true,
})

yank_cancel = function()
Expand Down
1 change: 1 addition & 0 deletions runtime/lua/vim/treesitter/_meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ error('Cannot require a meta file')
---@field descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
---@field named_descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
---@field parent fun(self: TSNode): TSNode?
---@field child_containing_descendant fun(self: TSNode, descendant: TSNode): TSNode?
---@field next_sibling fun(self: TSNode): TSNode?
---@field prev_sibling fun(self: TSNode): TSNode?
---@field next_named_sibling fun(self: TSNode): TSNode?
Expand Down
13 changes: 2 additions & 11 deletions runtime/lua/vim/treesitter/query.lua
Original file line number Diff line number Diff line change
Expand Up @@ -457,17 +457,8 @@ local predicate_handlers = {
end

for _, node in ipairs(nodes) do
local ancestor_types = {} --- @type table<string, boolean>
for _, type in ipairs({ unpack(predicate, 3) }) do
ancestor_types[type] = true
end

local cur = node:parent()
while cur do
if ancestor_types[cur:type()] then
return true
end
cur = cur:parent()
if node:__has_ancestor(predicate) then
return true
end
end
return false
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ find_package(Libuv 1.28.0 REQUIRED)
find_package(Libvterm 0.3.3 REQUIRED)
find_package(Lpeg REQUIRED)
find_package(Msgpack 1.0.0 REQUIRED)
find_package(Treesitter 0.20.9 REQUIRED)
find_package(Treesitter 0.22.6 REQUIRED)
find_package(Unibilium 2.0 REQUIRED)

target_link_libraries(main_lib INTERFACE
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/api/deprecated.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
DecorInline decor = { .ext = true, .data.ext.vt = vt, .data.ext.sh_idx = DECOR_ID_INVALID };

extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, 0, true,
false, false, false, false, NULL);
false, false, false, NULL);
return src_id;
}

Expand Down
60 changes: 47 additions & 13 deletions src/nvim/api/extmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ void api_extmark_free_all_mem(void)
xfree(name.data);
})
map_destroy(String, &namespace_ids);
set_destroy(uint32_t, &namespace_scoped);
}

/// Creates a new namespace or gets an existing one. [namespace]()
Expand Down Expand Up @@ -179,10 +180,6 @@ static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_na
PUT_C(dict, "invalid", BOOLEAN_OBJ(true));
}

if (mt_scoped(start)) {
PUT_C(dict, "scoped", BOOLEAN_OBJ(true));
}

decor_to_dict_legacy(&dict, mt_decor(start), hl_name, arena);

ADD_C(rv, DICTIONARY_OBJ(dict));
Expand Down Expand Up @@ -489,8 +486,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// used together with virt_text.
/// - url: A URL to associate with this extmark. In the TUI, the OSC 8 control
/// sequence is used to generate a clickable hyperlink to this URL.
/// - scoped: boolean (EXPERIMENTAL) enables "scoping" for the extmark. See
/// |nvim__win_add_ns()|
///
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
Expand Down Expand Up @@ -749,11 +744,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}

if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) {
if (opts->scoped) {
api_set_error(err, kErrorTypeException, "not yet implemented");
goto error;
}

int r = (int)line;
int c = (int)col;
if (line2 == -1) {
Expand Down Expand Up @@ -834,7 +824,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
decor, decor_flags, right_gravity, opts->end_right_gravity,
!GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore),
opts->invalidate, opts->scoped, err);
opts->invalidate, err);
if (ERROR_SET(err)) {
decor_free(decor);
return 0;
Expand Down Expand Up @@ -960,7 +950,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
decor.data.hl.hl_id = hl_id;

extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end,
decor, MT_FLAG_DECOR_HL, true, false, false, false, false, NULL);
decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL);
return ns_id;
}

Expand Down Expand Up @@ -1291,3 +1281,47 @@ Boolean nvim__win_del_ns(Window window, Integer ns_id, Error *err)

return true;
}

/// EXPERIMENTAL: this API will change in the future.
///
/// Set some properties for namespace
///
/// @param ns_id Namespace
/// @param opts Optional parameters to set:
/// - scoped: (boolean) Make the namespace a scoped namespace
///
void nvim__ns_set(Integer ns_id, Dict(ns_opts) *opts, Error *err)
{
VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return;
});

if (HAS_KEY(opts, ns_opts, scoped)) {
if (opts->scoped) {
set_put(uint32_t, &namespace_scoped, (uint32_t)ns_id);
} else {
set_del(uint32_t, &namespace_scoped, (uint32_t)ns_id);
}

changed_window_setting_all();
}
}

/// EXPERIMENTAL: this API will change in the future.
///
/// Get the properties for namespace
///
/// @param ns_id Namespace
/// @return Map defining the namespace properties, see |nvim__ns_set()|
Dict(ns_opts) nvim__ns_get(Integer ns_id, Error *err)
{
Dict(ns_opts) opts = KEYDICT_INIT;

VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return opts;
});

PUT_KEY(opts, ns_opts, scoped, set_has(uint32_t, &namespace_scoped, (uint32_t)ns_id));

return opts;
}
11 changes: 11 additions & 0 deletions src/nvim/api/extmark.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,25 @@

#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/buffer_defs.h"
#include "nvim/decoration_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/types_defs.h"

EXTERN Map(String, int) namespace_ids INIT( = MAP_INIT);
EXTERN Set(uint32_t) namespace_scoped INIT( = SET_INIT);
EXTERN handle_T next_namespace_id INIT( = 1);

static inline bool ns_scoped_in_win(uint32_t ns_id, win_T *wp)
{
if (!set_has(uint32_t, &namespace_scoped, ns_id)) {
return true;
}

return set_has(uint32_t, &wp->w_ns_set, ns_id);
}

#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.h.generated.h"
#endif
5 changes: 5 additions & 0 deletions src/nvim/api/keysets_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,8 @@ typedef struct {
Window win;
Buffer buf;
} Dict(redraw);

typedef struct {
OptionalKeys is_set__ns_opts_;
Boolean scoped;
} Dict(ns_opts);
8 changes: 4 additions & 4 deletions src/nvim/decoration.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start

extmark_set(buf, (uint32_t)src_id, NULL,
(int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end,
decor, MT_FLAG_DECOR_HL, true, false, true, false, false, NULL);
decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL);
}
}

Expand Down Expand Up @@ -580,7 +580,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s
break;
}

if (!mt_scoped_in_win(mark, wp)) {
if (!ns_scoped_in_win(mark.ns, wp)) {
goto next_mark;
}

Expand Down Expand Up @@ -729,7 +729,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[],
break;
}
if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)
&& mt_scoped_in_win(mark, wp)) {
&& ns_scoped_in_win(mark.ns, wp)) {
DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
num_text += (sh->text[0] != NUL);
kv_push(signs, ((SignItem){ sh, mark.id }));
Expand Down Expand Up @@ -908,7 +908,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
while (true) {
MTKey mark = marktree_itr_current(itr);
DecorVirtText *vt = mt_decor_virt(mark);
if (mt_scoped_in_win(mark, wp)) {
if (ns_scoped_in_win(mark.ns, wp)) {
while (vt) {
if (vt->flags & kVTIsLines) {
bool above = vt->flags & kVTLinesAbove;
Expand Down
4 changes: 2 additions & 2 deletions src/nvim/extmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@
/// must not be used during iteration!
void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
colnr_T end_col, DecorInline decor, uint16_t decor_flags, bool right_gravity,
bool end_right_gravity, bool no_undo, bool invalidate, bool scoped, Error *err)
bool end_right_gravity, bool no_undo, bool invalidate, Error *err)
{
uint32_t *ns = map_put_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL, NULL);
uint32_t id = idp ? *idp : 0;

uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext, scoped) | decor_flags;
uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext) | decor_flags;
if (id == 0) {
id = ++*ns;
} else {
Expand Down

0 comments on commit 7d963d4

Please sign in to comment.