Add multi selection support to UnwrapSyntaxNode (#35991)
Closes #35932 Closes #35933 I only intended to fix multi select in this, I accidentally drive-by fixed the VIM issue as well. `replace_text_in_range` which I was using before has two, to me unexpected, side-effects: - it no-ops when input is disabled, which is the case in VIM's Insert/Visual modes - it takes the current selection into account, and does not just operate on the given range (which I erroneously assumed before) Now the code is using `buffer.edit` instead, which seems more lower level, and does not have those side-effects. I was enthused to see that it accepts a vec of edits, so I didn't have to calculate offsets for following edits... until I also wanted to set selections, where I do need to do it by hand. I'm still wondering if there is a simpler way to do it, but for now it at least passes my muster Release Notes: - Added multiple selection support to UnwrapSyntaxNode action - Fixed UnwrapSyntaxNode not working in VIM Insert/Visual modes
This commit is contained in:
parent
9e0e233319
commit
bb640c6a1c
2 changed files with 50 additions and 52 deletions
|
@ -14834,15 +14834,18 @@ impl Editor {
|
||||||
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
|
||||||
|
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
|
let selections = self
|
||||||
|
.selections
|
||||||
|
.all::<usize>(cx)
|
||||||
|
.into_iter()
|
||||||
|
// subtracting the offset requires sorting
|
||||||
|
.sorted_by_key(|i| i.start);
|
||||||
|
|
||||||
let edits = old_selections
|
let full_edits = selections
|
||||||
.iter()
|
.into_iter()
|
||||||
// only consider the first selection for now
|
.filter_map(|selection| {
|
||||||
.take(1)
|
|
||||||
.map(|selection| {
|
|
||||||
// Only requires two branches once if-let-chains stabilize (#53667)
|
// Only requires two branches once if-let-chains stabilize (#53667)
|
||||||
let selection_range = if !selection.is_empty() {
|
let child = if !selection.is_empty() {
|
||||||
selection.range()
|
selection.range()
|
||||||
} else if let Some((_, ancestor_range)) =
|
} else if let Some((_, ancestor_range)) =
|
||||||
buffer.syntax_ancestor(selection.start..selection.end)
|
buffer.syntax_ancestor(selection.start..selection.end)
|
||||||
|
@ -14855,48 +14858,52 @@ impl Editor {
|
||||||
selection.range()
|
selection.range()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_range = selection_range.clone();
|
let mut parent = child.clone();
|
||||||
while let Some((_, ancestor_range)) = buffer.syntax_ancestor(new_range.clone()) {
|
while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
|
||||||
new_range = match ancestor_range {
|
parent = match ancestor_range {
|
||||||
MultiOrSingleBufferOffsetRange::Single(range) => range,
|
MultiOrSingleBufferOffsetRange::Single(range) => range,
|
||||||
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
MultiOrSingleBufferOffsetRange::Multi(range) => range,
|
||||||
};
|
};
|
||||||
if new_range.start < selection_range.start
|
if parent.start < child.start || parent.end > child.end {
|
||||||
|| new_range.end > selection_range.end
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(selection, selection_range, new_range)
|
if parent == child {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let text = buffer.text_for_range(child.clone()).collect::<String>();
|
||||||
|
Some((selection.id, parent, text))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.transact(window, cx, |editor, window, cx| {
|
self.transact(window, cx, |this, window, cx| {
|
||||||
for (_, child, parent) in &edits {
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
let text = buffer.text_for_range(child.clone()).collect::<String>();
|
buffer.edit(
|
||||||
editor.replace_text_in_range(Some(parent.clone()), &text, window, cx);
|
full_edits
|
||||||
}
|
.iter()
|
||||||
|
.map(|(_, p, t)| (p.clone(), t.clone()))
|
||||||
editor.change_selections(
|
.collect::<Vec<_>>(),
|
||||||
SelectionEffects::scroll(Autoscroll::fit()),
|
None,
|
||||||
window,
|
cx,
|
||||||
cx,
|
);
|
||||||
|s| {
|
});
|
||||||
s.select(
|
this.change_selections(Default::default(), window, cx, |s| {
|
||||||
edits
|
let mut offset = 0;
|
||||||
.iter()
|
let mut selections = vec![];
|
||||||
.map(|(s, old, new)| Selection {
|
for (id, parent, text) in full_edits {
|
||||||
id: s.id,
|
let start = parent.start - offset;
|
||||||
start: new.start,
|
offset += parent.len() - text.len();
|
||||||
end: new.start + old.len(),
|
selections.push(Selection {
|
||||||
goal: SelectionGoal::None,
|
id: id,
|
||||||
reversed: s.reversed,
|
start,
|
||||||
})
|
end: start + text.len(),
|
||||||
.collect(),
|
reversed: false,
|
||||||
);
|
goal: Default::default(),
|
||||||
},
|
});
|
||||||
);
|
}
|
||||||
|
s.select(selections);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8015,7 +8015,7 @@ async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppConte
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) {
|
async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
||||||
let mut cx = EditorTestContext::new(cx).await;
|
let mut cx = EditorTestContext::new(cx).await;
|
||||||
|
@ -8029,21 +8029,12 @@ async fn test_unwrap_syntax_node(cx: &mut gpui::TestAppContext) {
|
||||||
buffer.set_language(Some(language), cx);
|
buffer.set_language(Some(language), cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.set_state(
|
cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
|
||||||
&r#"
|
|
||||||
use mod1::mod2::{«mod3ˇ», mod4};
|
|
||||||
"#
|
|
||||||
.unindent(),
|
|
||||||
);
|
|
||||||
cx.update_editor(|editor, window, cx| {
|
cx.update_editor(|editor, window, cx| {
|
||||||
editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
|
editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
|
||||||
});
|
});
|
||||||
cx.assert_editor_state(
|
|
||||||
&r#"
|
cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
|
||||||
use mod1::mod2::«mod3ˇ»;
|
|
||||||
"#
|
|
||||||
.unindent(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue