From d27856b044bb1f878e8c5aa4149b893347855f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Hrastnik?= Date: Sun, 7 Jun 2026 21:31:33 +0900 Subject: [PATCH] helix-core: Make textobjects injection-aware --- helix-core/src/movement.rs | 15 ++++++++++----- helix-core/src/textobject.rs | 10 ++++++---- helix-term/src/commands.rs | 3 +-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index 09a99db2..82fe9f9b 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -558,23 +558,28 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo } } -/// Finds the range of the next or previous textobject in the syntax sub-tree of `node`. +/// Finds the range of the next or previous textobject in the syntax tree. /// Returns the range in the forwards direction. -#[allow(clippy::too_many_arguments)] pub fn goto_treesitter_object( slice: RopeSlice, range: Range, object_name: &str, dir: Direction, - slice_tree: &Node, syntax: &Syntax, loader: &syntax::Loader, count: usize, ) -> Range { - let textobject_query = loader.textobject_query(syntax.root_language()); let get_range = move |range: Range| -> Option { let byte_pos = slice.char_to_byte(range.cursor(slice)); + // Walk the layer at the cursor with that language's own tree and textobject query. + // Resolved per step so the motion can cross into and out of injected regions. + let layer = syntax.layer_for_byte_range(byte_pos as u32, byte_pos as u32); + let slice_tree = syntax + .tree_for_byte_range(byte_pos as u32, byte_pos as u32) + .root_node(); + let textobject_query = loader.textobject_query(syntax.layer(layer).language); + let cap_name = |t: TextObject| format!("{}.{}", object_name, t); let nodes = textobject_query?.capture_nodes_any( &[ @@ -582,7 +587,7 @@ pub fn goto_treesitter_object( &cap_name(TextObject::Around), &cap_name(TextObject::Inside), ], - slice_tree, + &slice_tree, slice, )?; diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index f04ab77e..1a40ddaf 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -263,11 +263,13 @@ pub fn textobject_treesitter( loader: &syntax::Loader, _count: usize, ) -> Range { - let root = syntax.tree().root_node(); - let textobject_query = loader.textobject_query(syntax.root_language()); + let byte_pos = slice.char_to_byte(range.cursor(slice)); + let layer = syntax.layer_for_byte_range(byte_pos as u32, byte_pos as u32); + let root = syntax + .tree_for_byte_range(byte_pos as u32, byte_pos as u32) + .root_node(); + let textobject_query = loader.textobject_query(syntax.layer(layer).language); let get_range = move || -> Option { - let byte_pos = slice.char_to_byte(range.cursor(slice)); - let capture_name = format!("{}.{}", object_name, textobject); // eg. function.inner let node = textobject_query? .capture_nodes(&capture_name, &root, slice)? diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 81c2cc1a..1c859d52 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6095,11 +6095,10 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct let loader = editor.syn_loader.load(); if let Some(syntax) = doc.syntax() { let text = doc.text().slice(..); - let root = syntax.tree().root_node(); let selection = doc.selection(view.id).clone().transform(|range| { let new_range = movement::goto_treesitter_object( - text, range, object, direction, &root, syntax, &loader, count, + text, range, object, direction, syntax, &loader, count, ); if editor.mode == Mode::Select { -- 2.51.2