Skip to content

Commit 3c6877b

Browse files
committed
add tree sitter match limit to avoid slowdowns for larger files
Affects all tree sitter queries and should speedup both syntax highlighting and text object queries. This has been shown to fix significant slowdowns with textobjects for rust files as small as 3k loc.
1 parent 758bace commit 3c6877b

File tree

1 file changed

+24
-0
lines changed

1 file changed

+24
-0
lines changed

helix-core/src/syntax.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,26 @@ impl<'a> CapturedNode<'a> {
354354
}
355355
}
356356

357+
/// The number of matches a TS cursor can at once to avoid performance problems for medium to large files.
358+
/// Set with `set_match_limit`.
359+
/// Using such a limit means that we loose valid captures in, so there is fundamentally a tradeoff here.
360+
///
361+
///
362+
/// Old tree sitter versions used a limit of 32 by default until this limit was removed in version `0.19.5` (must now best set manually).
363+
/// However, this causes performance issues for medium to large files.
364+
/// In helix, this problem caused treesitter motions to take multiple seconds to complete in medium-sized rust files (3k loc).
365+
/// Neovim also encountered this problem and reintroduced this limit after it was removed upstream
366+
/// (see <https://github.com/neovim/neovim/issues/14897> and <https://github.com/neovim/neovim/pull/14915>).
367+
/// The number used here is fundamentally a tradeoff between breaking some obscure edge cases and performance.
368+
///
369+
///
370+
/// A value of 64 was chosen because neovim uses that value.
371+
/// Neovim chose this value somewhat arbitrarily (<https://github.com/neovim/neovim/pull/18397>) adjusting it whenever issues occur in practice.
372+
/// However this value has been in use for a long time and due to the large userbase of neovim it is probably a good choice.
373+
/// If this limit causes problems for a grammar in the future, it could be increased.
374+
375+
const TREE_SITTER_MATCH_LIMIT: u32 = 64;
376+
357377
impl TextObjectQuery {
358378
/// Run the query on the given node and return sub nodes which match given
359379
/// capture ("function.inside", "class.around", etc).
@@ -394,6 +414,8 @@ impl TextObjectQuery {
394414
.iter()
395415
.find_map(|cap| self.query.capture_index_for_name(cap))?;
396416

417+
cursor.set_match_limit(TREE_SITTER_MATCH_LIMIT);
418+
397419
let nodes = cursor
398420
.captures(&self.query, node, RopeProvider(slice))
399421
.filter_map(move |(mat, _)| {
@@ -843,6 +865,7 @@ impl Syntax {
843865
let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
844866
// TODO: might need to set cursor range
845867
cursor.set_byte_range(0..usize::MAX);
868+
cursor.set_match_limit(TREE_SITTER_MATCH_LIMIT);
846869

847870
let source_slice = source.slice(..);
848871

@@ -1032,6 +1055,7 @@ impl Syntax {
10321055

10331056
// if reusing cursors & no range this resets to whole range
10341057
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
1058+
cursor_ref.set_match_limit(TREE_SITTER_MATCH_LIMIT);
10351059

10361060
let mut captures = cursor_ref
10371061
.captures(

0 commit comments

Comments
 (0)