From 0ab1a3f97358eb5861ed08b8075cf68f38fdead3 Mon Sep 17 00:00:00 2001 From: Oliver Sargison Date: Sun, 15 Sep 2024 14:29:51 +1200 Subject: [PATCH 1/2] Add function parameter and return commands --- helix-core/src/movement.rs | 48 +++++++++++ helix-term/src/commands.rs | 122 ++++++++++++++++++++++++++- helix-term/src/keymap/default.rs | 2 + runtime/queries/rust/textobjects.scm | 5 ++ 4 files changed, 174 insertions(+), 3 deletions(-) diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index e446d8cc425d..44f3bd8b505f 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -558,6 +558,54 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo } } +pub fn goto_current_function_parameters( + slice: RopeSlice, + range: Range, + slice_tree: Node, + lang_config: &LanguageConfiguration, +) -> Option { + let byte_pos = slice.char_to_byte(range.cursor(slice)); + + let mut cursor = QueryCursor::new(); + let current_function_range = current_node_byte_range( + lang_config, + slice_tree, + slice, + &mut cursor, + "function.around", + byte_pos, + )?; + + cursor.set_byte_range(current_function_range); + + let parameters_node = lang_config + .textobject_query()? + .capture_nodes_any(&["parameters.around"], slice_tree, slice, &mut cursor)? + .next()?; + + Some(Range::new( + slice.byte_to_char(parameters_node.start_byte()), + slice.byte_to_char(parameters_node.end_byte()), + )) +} + +pub fn current_node_byte_range( + lang_config: &LanguageConfiguration, + slice_tree: Node<'_>, + slice: RopeSlice<'_>, + cursor: &mut QueryCursor, + node: &'static str, + byte_pos: usize, +) -> Option> { + let current_function_range = lang_config + .textobject_query()? + .capture_nodes_any(&[node], slice_tree, slice, cursor)? + .filter(|func| func.byte_range().contains(&byte_pos)) + .max_by_key(|func| func.end_byte())? + .byte_range(); + Some(current_function_range) +} + /// Finds the range of the next or previous textobject in the syntax sub-tree of `node`. /// Returns the range in the forwards direction. pub fn goto_treesitter_object( diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 6e037a471ffc..867dadf75dc3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -22,11 +22,11 @@ use helix_core::{ encoding, find_workspace, graphemes::{self, next_grapheme_boundary, RevRopeGraphemes}, history::UndoKind, - increment, indent, - indent::IndentStyle, + increment, + indent::{self, IndentStyle}, line_ending::{get_line_ending_of_str, line_end_char_index}, match_brackets, - movement::{self, move_vertically_visual, Direction}, + movement::{self, current_node_byte_range, move_vertically_visual, Direction}, object, pos_at_coords, regex::{self, Regex}, search::{self, CharMatcher}, @@ -396,6 +396,7 @@ impl MappableCommand { goto_declaration, "Goto declaration", add_newline_above, "Add newline above", add_newline_below, "Add newline below", + add_function_parameter, "Add function parameter", goto_type_definition, "Goto type definition", goto_implementation, "Goto implementation", goto_file_start, "Goto line number else file start", @@ -426,6 +427,7 @@ impl MappableCommand { goto_previous_buffer, "Goto previous buffer", goto_line_end_newline, "Goto newline at line end", goto_first_nonwhitespace, "Goto first non-blank in line", + goto_return_type, "Goto the return type of the current function", trim_selections, "Trim whitespace from selections", extend_to_line_start, "Extend to line start", extend_to_first_nonwhitespace, "Extend to first non-blank in line", @@ -5968,6 +5970,120 @@ fn add_newline_impl(cx: &mut Context, open: Open) { doc.apply(&transaction, view.id); } +fn add_function_parameter(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text(); + let slice = text.slice(..); + + if let Some((lang_config, syntax)) = doc.language_config().zip(doc.syntax()) { + let root = syntax.tree().root_node(); + + let selection = doc.selection(view.id).clone().into_single(); + + let Some(new_range) = movement::goto_current_function_parameters( + slice, + selection.ranges()[0], + root, + lang_config, + ) else { + return; + }; + + let mut cursor = helix_core::tree_sitter::QueryCursor::new(); + cursor.set_match_limit(1); + cursor + .set_byte_range(text.char_to_byte(new_range.anchor)..text.char_to_byte(new_range.head)); + + let has_args = lang_config + .textobject_query() + .and_then(|q| { + q.capture_nodes_any(&["parameter.around"], root, slice, &mut cursor)? + .next() + }) + .is_some(); + + push_jump(view, doc); + + doc.set_selection(view.id, selection.transform(|_| new_range)); + if has_args { + insert_char(cx, ','); + } + collapse_selection(cx); + insert_mode(cx); + } + // }; +} + +fn goto_return_type(cx: &mut Context) { + let (view, doc) = current!(cx.editor); + let text = doc.text(); + let slice = text.slice(..); + + if let Some((lang_config, syntax)) = doc.language_config().zip(doc.syntax()) { + let root = syntax.tree().root_node(); + + let selection = doc.selection(view.id).clone().into_single(); + let range = selection.ranges()[0]; + let mut cursor = helix_core::tree_sitter::QueryCursor::new(); + let byte_pos = slice.char_to_byte(range.cursor(slice)); + let Some(func_range) = current_node_byte_range( + lang_config, + root, + slice, + &mut cursor, + "function.around", + byte_pos, + ) else { + return; + }; + + cursor.set_match_limit(1); + cursor.set_byte_range(func_range.clone()); + + let new_byte_range = if let Some(return_node) = + lang_config.textobject_query().and_then(|q| { + q.capture_nodes_any(&["return_type.around"], root, slice, &mut cursor)? + .next() + }) { + return_node.byte_range() + } else { + // let parameters_range = + let parameters_node = lang_config.textobject_query().and_then(|q| { + q.capture_nodes_any(&["parameters.around"], root, slice, &mut cursor)? + .next() + .map(|node| node.byte_range()) + }); + + cursor.set_byte_range(0..func_range.end + 1); + let function_body_range = current_node_byte_range( + lang_config, + root, + slice, + &mut cursor, + "function.inside", + byte_pos, + ); + + match (parameters_node, function_body_range) { + (Some(pr), Some(br)) => pr.end..br.start, + (_, Some(br)) => br.start..br.start, + (Some(pr), _) => pr.end..pr.end, + _ => { + return; + } + } + }; + + push_jump(view, doc); + let new_range = Range::new( + slice.byte_to_char(new_byte_range.start), + slice.byte_to_char(new_byte_range.end), + ); + doc.set_selection(view.id, selection.transform(|_| new_range)); + } + // }; +} + enum IncrementDirection { Increase, Decrease, diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 5a3e8eed42da..e84a68977bb0 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -47,6 +47,7 @@ pub fn default() -> HashMap { "D" => goto_declaration, "y" => goto_type_definition, "r" => goto_reference, + "R" => goto_return_type, "i" => goto_implementation, "t" => goto_window_top, "c" => goto_window_center, @@ -285,6 +286,7 @@ pub fn default() -> HashMap { "c" => toggle_comments, "C" => toggle_block_comments, "A-c" => toggle_line_comments, + "i" => add_function_parameter, "?" => command_palette, }, "z" => { "View" diff --git a/runtime/queries/rust/textobjects.scm b/runtime/queries/rust/textobjects.scm index de517d362408..b9b4ed3d6431 100644 --- a/runtime/queries/rust/textobjects.scm +++ b/runtime/queries/rust/textobjects.scm @@ -1,6 +1,11 @@ (function_item body: (_) @function.inside) @function.around +(parameters) @parameters.around + +(function_item + return_type: (_) @return_type.around ) + (closure_expression body: (_) @function.inside) @function.around From 2f139d8313d7357c424a7e3937a554666a22fdcf Mon Sep 17 00:00:00 2001 From: Oliver Sargison Date: Mon, 16 Dec 2024 01:02:06 +1300 Subject: [PATCH 2/2] Change parameter insert to deal with commas --- helix-term/src/commands.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 867dadf75dc3..213e27b69101 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -63,6 +63,7 @@ use crate::{ }; use crate::job::{self, Jobs}; + use std::{ cmp::Ordering, collections::{HashMap, HashSet}, @@ -5994,18 +5995,20 @@ fn add_function_parameter(cx: &mut Context) { cursor .set_byte_range(text.char_to_byte(new_range.anchor)..text.char_to_byte(new_range.head)); - let has_args = lang_config + let should_add_comma = lang_config .textobject_query() .and_then(|q| { q.capture_nodes_any(&["parameter.around"], root, slice, &mut cursor)? - .next() + .last() }) - .is_some(); + .map_or(false, |cn| { + !cn.byte_range().any(|i| slice.get_char(i) == Some(',')) + }); push_jump(view, doc); doc.set_selection(view.id, selection.transform(|_| new_range)); - if has_args { + if should_add_comma { insert_char(cx, ','); } collapse_selection(cx);