diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 3a3a57ca64..39433b3c27 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -745,5 +745,6 @@ actions!( UniqueLinesCaseInsensitive, /// Removes duplicate lines (case-sensitive). UniqueLinesCaseSensitive, + UnwrapSyntaxNode ] ); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 156fda1b37..73a81bea19 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -14711,6 +14711,81 @@ impl Editor { } } + pub fn unwrap_syntax_node( + &mut self, + _: &UnwrapSyntaxNode, + window: &mut Window, + cx: &mut Context, + ) { + self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx); + + let buffer = self.buffer.read(cx).snapshot(cx); + let old_selections: Box<[_]> = self.selections.all::(cx).into(); + + let edits = old_selections + .iter() + // only consider the first selection for now + .take(1) + .map(|selection| { + // Only requires two branches once if-let-chains stabilize (#53667) + let selection_range = if !selection.is_empty() { + selection.range() + } else if let Some((_, ancestor_range)) = + buffer.syntax_ancestor(selection.start..selection.end) + { + match ancestor_range { + MultiOrSingleBufferOffsetRange::Single(range) => range, + MultiOrSingleBufferOffsetRange::Multi(range) => range, + } + } else { + selection.range() + }; + + let mut new_range = selection_range.clone(); + while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) { + new_range = match ancestor_range { + MultiOrSingleBufferOffsetRange::Single(range) => range, + MultiOrSingleBufferOffsetRange::Multi(range) => range, + }; + if new_range.start < selection_range.start + || new_range.end > selection_range.end + { + break; + } + } + + (selection, selection_range, new_range) + }) + .collect::>(); + + self.transact(window, cx, |editor, window, cx| { + for (_, child, parent) in &edits { + let text = buffer.text_for_range(child.clone()).collect::(); + editor.replace_text_in_range(Some(parent.clone()), &text, window, cx); + } + + editor.change_selections( + SelectionEffects::scroll(Autoscroll::fit()), + window, + cx, + |s| { + s.select( + edits + .iter() + .map(|(s, old, new)| Selection { + id: s.id, + start: new.start, + end: new.start + old.len(), + goal: SelectionGoal::None, + reversed: s.reversed, + }) + .collect(), + ); + }, + ); + }); + } + fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context) -> Task<()> { if !EditorSettings::get_global(cx).gutter.runnables { self.clear_tasks(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1cb3565733..b31963c9c8 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7969,6 +7969,38 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte }); } +#[gpui::test] +async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + + let language = Arc::new(Language::new( + LanguageConfig::default(), + Some(tree_sitter_rust::LANGUAGE.into()), + )); + + cx.update_buffer(|buffer, cx| { + buffer.set_language(Some(language), cx); + }); + + cx.set_state( + &r#" + use mod1::mod2::{«mod3ˇ», mod4}; + "# + .unindent(), + ); + cx.update_editor(|editor, window, cx| { + editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx); + }); + cx.assert_editor_state( + &r#" + use mod1::mod2::«mod3ˇ»; + "# + .unindent(), + ); +} + #[gpui::test] async fn test_fold_function_bodies(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e1647215bc..17a43f9640 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -357,6 +357,7 @@ impl EditorElement { register_action(editor, window, Editor::toggle_comments); register_action(editor, window, Editor::select_larger_syntax_node); register_action(editor, window, Editor::select_smaller_syntax_node); + register_action(editor, window, Editor::unwrap_syntax_node); register_action(editor, window, Editor::select_enclosing_symbol); register_action(editor, window, Editor::move_to_enclosing_bracket); register_action(editor, window, Editor::undo_selection);