Add UnwrapSyntaxNode action (#31421)
Remake of #8967 > Hey there, > > I have started relying on this action, that I've also put into VSCode as [an extension](https://github.com/Gregoor/soy). On some level I don't know how people code (cope?) without it: > > Release Notes: > > * Added UnwrapSyntaxNode action > > https://github.com/zed-industries/zed/assets/4051932/d74c98c0-96d8-4075-9b63-cea55bea42f6 > > Since I had to put it into Zed anyway to make it my daily driver, I thought I'd also check here if there's an interest in shipping it by default (that would ofc also personally make my life better, not having to maintain my personal fork and all). > > If there is interest, I'd be happy to make any changes to make this more mergeable. Two TODOs on my mind are: > > * unwrap multiple into single (e.g. `fn(≤a≥, b)` to `fn(≤a≥)`) > * multi-cursor > * syntax awareness, i.e. only unwrap if it does not break syntax (I added [a coarse version of that for my VSC extension](https://github.com/Gregoor/soy/blob/main/src/actions/unwrap.ts#L29)) > > Somewhat off-topic: I was happy to see that you're [also](https://github.com/Gregoor/soy/blob/main/src/actions/unwrap.test.ts) using rare special chars in test code to denote cursor positions. Release Notes: - Added UnwrapSyntaxNode action --------- Co-authored-by: Peter Tripp <peter@zed.dev>
This commit is contained in:
parent
f1e69f6311
commit
b4a441f12f
4 changed files with 109 additions and 0 deletions
|
@ -745,5 +745,6 @@ actions!(
|
|||
UniqueLinesCaseInsensitive,
|
||||
/// Removes duplicate lines (case-sensitive).
|
||||
UniqueLinesCaseSensitive,
|
||||
UnwrapSyntaxNode
|
||||
]
|
||||
);
|
||||
|
|
|
@ -14711,6 +14711,81 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_syntax_node(
|
||||
&mut self,
|
||||
_: &UnwrapSyntaxNode,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||
|
||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||
let old_selections: Box<[_]> = self.selections.all::<usize>(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::<Vec<_>>();
|
||||
|
||||
self.transact(window, cx, |editor, window, cx| {
|
||||
for (_, child, parent) in &edits {
|
||||
let text = buffer.text_for_range(child.clone()).collect::<String>();
|
||||
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<Self>) -> Task<()> {
|
||||
if !EditorSettings::get_global(cx).gutter.runnables {
|
||||
self.clear_tasks();
|
||||
|
|
|
@ -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, |_| {});
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue