on master help sign in
Add tree-sitter based function, class navigation
966fbc5Gokul Soumya
helix-core/src/movement.rs+51−1
@@ -1,6 +1,7 @@
1use std::iter;
1use std::iter;
2
2
3use ropey::iter::Chars;
3use ropey::iter::Chars;
4+use tree_sitter::{Node, QueryCursor};
4
5
5use crate::{
6use crate::{
6 chars::{categorize_char, char_is_line_ending, CharCategory},
7 chars::{categorize_char, char_is_line_ending, CharCategory},
@@ -9,7 +10,10 @@ use crate::{
9 next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
10 next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
10 prev_grapheme_boundary,
11 prev_grapheme_boundary,
11 },
12 },
13+ pos_at_coords,
14+ syntax::LanguageConfiguration,
15+ textobject::TextObject,
12− pos_at_coords, Position, Range, RopeSlice,
16+ Position, Range, RopeSlice,
13};
17};
14
18
15#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -305,6 +309,52 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
305 }
309 }
306}
310}
307
311
312+pub fn goto_treesitter_object(
313+ slice: RopeSlice,
314+ range: Range,
315+ object_name: &str,
316+ dir: Direction,
317+ slice_tree: Node,
318+ lang_config: &LanguageConfiguration,
319+ _count: usize,
320+) -> Range {
321+ let get_range = move || -> Option<Range> {
322+ let byte_pos = slice.char_to_byte(range.cursor(slice));
323+
324+ let capture_name = format!("{}.{}", object_name, TextObject::Around);
325+ let mut cursor = QueryCursor::new();
326+ let nodes = lang_config.textobject_query()?.capture_nodes(
327+ &capture_name,
328+ slice_tree,
329+ slice,
330+ &mut cursor,
331+ )?;
332+
333+ let node = match dir {
334+ Direction::Forward => nodes
335+ .filter(|n| n.start_byte() > byte_pos)
336+ .min_by_key(|n| n.start_byte())?,
337+ Direction::Backward => nodes
338+ .filter(|n| n.start_byte() < byte_pos)
339+ .max_by_key(|n| n.start_byte())?,
340+ };
341+
342+ let len = slice.len_bytes();
343+ let start_byte = node.start_byte();
344+ let end_byte = node.end_byte();
345+ if start_byte >= len || end_byte >= len {
346+ return None;
347+ }
348+
349+ let start_char = slice.byte_to_char(start_byte);
350+ let end_char = slice.byte_to_char(end_byte);
351+
352+ // head of range should be at beginning
353+ Some(Range::new(end_char, start_char))
354+ };
355+ get_range().unwrap_or(range)
356+}
357+
308#[cfg(test)]
358#[cfg(test)]
309mod test {
359mod test {
310 use ropey::Rope;
360 use ropey::Rope;
helix-term/src/commands.rs+52
@@ -398,6 +398,12 @@ impl MappableCommand {
398 surround_delete, "Surround delete",
398 surround_delete, "Surround delete",
399 select_textobject_around, "Select around object",
399 select_textobject_around, "Select around object",
400 select_textobject_inner, "Select inside object",
400 select_textobject_inner, "Select inside object",
401+ goto_next_function, "Goto next function",
402+ goto_prev_function, "Goto previous function",
403+ goto_next_class, "Goto next class",
404+ goto_prev_class, "Goto previous class",
405+ goto_next_parameter, "Goto next parameter",
406+ goto_prev_parameter, "Goto previous parameter",
401 dap_launch, "Launch debug target",
407 dap_launch, "Launch debug target",
402 dap_toggle_breakpoint, "Toggle breakpoint",
408 dap_toggle_breakpoint, "Toggle breakpoint",
403 dap_continue, "Continue program execution",
409 dap_continue, "Continue program execution",
@@ -5907,6 +5913,52 @@ fn scroll_down(cx: &mut Context) {
5907 scroll(cx, cx.count(), Direction::Forward);
5913 scroll(cx, cx.count(), Direction::Forward);
5908}
5914}
5909
5915
5916+fn goto_ts_object_impl(cx: &mut Context, object: &str, direction: Direction) {
5917+ let count = cx.count();
5918+ let (view, doc) = current!(cx.editor);
5919+ let text = doc.text().slice(..);
5920+ let range = doc.selection(view.id).primary();
5921+
5922+ let new_range = match doc.language_config().zip(doc.syntax()) {
5923+ Some((lang_config, syntax)) => movement::goto_treesitter_object(
5924+ text,
5925+ range,
5926+ object,
5927+ direction,
5928+ syntax.tree().root_node(),
5929+ lang_config,
5930+ count,
5931+ ),
5932+ None => range,
5933+ };
5934+
5935+ doc.set_selection(view.id, Selection::single(new_range.anchor, new_range.head));
5936+}
5937+
5938+fn goto_next_function(cx: &mut Context) {
5939+ goto_ts_object_impl(cx, "function", Direction::Forward)
5940+}
5941+
5942+fn goto_prev_function(cx: &mut Context) {
5943+ goto_ts_object_impl(cx, "function", Direction::Backward)
5944+}
5945+
5946+fn goto_next_class(cx: &mut Context) {
5947+ goto_ts_object_impl(cx, "class", Direction::Forward)
5948+}
5949+
5950+fn goto_prev_class(cx: &mut Context) {
5951+ goto_ts_object_impl(cx, "class", Direction::Backward)
5952+}
5953+
5954+fn goto_next_parameter(cx: &mut Context) {
5955+ goto_ts_object_impl(cx, "parameter", Direction::Forward)
5956+}
5957+
5958+fn goto_prev_parameter(cx: &mut Context) {
5959+ goto_ts_object_impl(cx, "parameter", Direction::Backward)
5960+}
5961+
5910fn select_textobject_around(cx: &mut Context) {
5962fn select_textobject_around(cx: &mut Context) {
5911 select_textobject(cx, textobject::TextObject::Around);
5963 select_textobject(cx, textobject::TextObject::Around);
5912}
5964}
helix-term/src/keymap.rs+6
@@ -572,11 +572,17 @@ impl Default for Keymaps {
572 "[" => { "Left bracket"
572 "[" => { "Left bracket"
573 "d" => goto_prev_diag,
573 "d" => goto_prev_diag,
574 "D" => goto_first_diag,
574 "D" => goto_first_diag,
575+ "f" => goto_prev_function,
576+ "c" => goto_prev_class,
577+ "p" => goto_prev_parameter,
575 "space" => add_newline_above,
578 "space" => add_newline_above,
576 },
579 },
577 "]" => { "Right bracket"
580 "]" => { "Right bracket"
578 "d" => goto_next_diag,
581 "d" => goto_next_diag,
579 "D" => goto_last_diag,
582 "D" => goto_last_diag,
583+ "f" => goto_next_function,
584+ "c" => goto_next_class,
585+ "p" => goto_next_parameter,
580 "space" => add_newline_below,
586 "space" => add_newline_below,
581 },
587 },
582
588