mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-06 02:37:21 +00:00
Merge pull request #1275 from zed-industries/delete-autoclose-pair
Delete the autoclosing bracket when deleting the opening bracket
This commit is contained in:
commit
317060913c
1 changed files with 198 additions and 51 deletions
|
@ -2132,6 +2132,41 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_autoclose_pair(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
|
let old_selections = self.selections.all::<usize>(cx);
|
||||||
|
let autoclose_pair = if let Some(autoclose_pair) = self.autoclose_stack.last() {
|
||||||
|
autoclose_pair
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert_eq!(old_selections.len(), autoclose_pair.ranges.len());
|
||||||
|
|
||||||
|
let mut new_selections = Vec::new();
|
||||||
|
for (selection, autoclose_range) in old_selections
|
||||||
|
.iter()
|
||||||
|
.zip(autoclose_pair.ranges.iter().map(|r| r.to_offset(&buffer)))
|
||||||
|
{
|
||||||
|
if selection.is_empty() && autoclose_range.is_empty() && selection.start == autoclose_range.start {
|
||||||
|
new_selections.push(Selection {
|
||||||
|
id: selection.id,
|
||||||
|
start: selection.start - autoclose_pair.pair.start.len(),
|
||||||
|
end: selection.end + autoclose_pair.pair.end.len(),
|
||||||
|
reversed: true,
|
||||||
|
goal: selection.goal,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::Fit), cx, |selections| {
|
||||||
|
selections.select(new_selections)
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
|
fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
|
||||||
let offset = position.to_offset(buffer);
|
let offset = position.to_offset(buffer);
|
||||||
let (word_range, kind) = buffer.surrounding_word(offset);
|
let (word_range, kind) = buffer.surrounding_word(offset);
|
||||||
|
@ -2776,46 +2811,52 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
pub fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
self.transact(cx, |this, cx| {
|
||||||
let mut selections = self.selections.all::<Point>(cx);
|
if !this.select_autoclose_pair(cx) {
|
||||||
if !self.selections.line_mode {
|
let mut selections = this.selections.all::<Point>(cx);
|
||||||
for selection in &mut selections {
|
if !this.selections.line_mode {
|
||||||
if selection.is_empty() {
|
let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let old_head = selection.head();
|
for selection in &mut selections {
|
||||||
let mut new_head =
|
if selection.is_empty() {
|
||||||
movement::left(&display_map, old_head.to_display_point(&display_map))
|
let old_head = selection.head();
|
||||||
|
let mut new_head = movement::left(
|
||||||
|
&display_map,
|
||||||
|
old_head.to_display_point(&display_map),
|
||||||
|
)
|
||||||
.to_point(&display_map);
|
.to_point(&display_map);
|
||||||
if let Some((buffer, line_buffer_range)) = display_map
|
if let Some((buffer, line_buffer_range)) = display_map
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.buffer_line_for_row(old_head.row)
|
.buffer_line_for_row(old_head.row)
|
||||||
{
|
{
|
||||||
let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
|
let indent_size =
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
buffer.indent_size_for_line(line_buffer_range.start.row);
|
||||||
let indent_len = match indent_size.kind {
|
let language_name =
|
||||||
IndentKind::Space => {
|
buffer.language().map(|language| language.name());
|
||||||
cx.global::<Settings>().tab_size(language_name.as_deref())
|
let indent_len = match indent_size.kind {
|
||||||
|
IndentKind::Space => {
|
||||||
|
cx.global::<Settings>().tab_size(language_name.as_deref())
|
||||||
|
}
|
||||||
|
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
||||||
|
};
|
||||||
|
if old_head.column <= indent_size.len && old_head.column > 0 {
|
||||||
|
let indent_len = indent_len.get();
|
||||||
|
new_head = cmp::min(
|
||||||
|
new_head,
|
||||||
|
Point::new(
|
||||||
|
old_head.row,
|
||||||
|
((old_head.column - 1) / indent_len) * indent_len,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IndentKind::Tab => NonZeroU32::new(1).unwrap(),
|
|
||||||
};
|
selection.set_head(new_head, SelectionGoal::None);
|
||||||
if old_head.column <= indent_size.len && old_head.column > 0 {
|
|
||||||
let indent_len = indent_len.get();
|
|
||||||
new_head = cmp::min(
|
|
||||||
new_head,
|
|
||||||
Point::new(
|
|
||||||
old_head.row,
|
|
||||||
((old_head.column - 1) / indent_len) * indent_len,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.set_head(new_head, SelectionGoal::None);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.transact(cx, |this, cx| {
|
this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
|
||||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| s.select(selections));
|
}
|
||||||
this.insert("", cx);
|
this.insert("", cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3749,15 +3790,17 @@ impl Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
if !this.select_autoclose_pair(cx) {
|
||||||
let line_mode = s.line_mode;
|
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
let line_mode = s.line_mode;
|
||||||
if selection.is_empty() && !line_mode {
|
s.move_with(|map, selection| {
|
||||||
let cursor = movement::previous_word_start(map, selection.head());
|
if selection.is_empty() && !line_mode {
|
||||||
selection.set_head(cursor, SelectionGoal::None);
|
let cursor = movement::previous_word_start(map, selection.head());
|
||||||
}
|
selection.set_head(cursor, SelectionGoal::None);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
this.insert("", cx);
|
this.insert("", cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3768,15 +3811,17 @@ impl Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
if !this.select_autoclose_pair(cx) {
|
||||||
let line_mode = s.line_mode;
|
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
let line_mode = s.line_mode;
|
||||||
if selection.is_empty() && !line_mode {
|
s.move_with(|map, selection| {
|
||||||
let cursor = movement::previous_subword_start(map, selection.head());
|
if selection.is_empty() && !line_mode {
|
||||||
selection.set_head(cursor, SelectionGoal::None);
|
let cursor = movement::previous_subword_start(map, selection.head());
|
||||||
}
|
selection.set_head(cursor, SelectionGoal::None);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
this.insert("", cx);
|
this.insert("", cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -8964,7 +9009,7 @@ mod tests {
|
||||||
a
|
a
|
||||||
b
|
b
|
||||||
c
|
c
|
||||||
"#
|
"#
|
||||||
.unindent();
|
.unindent();
|
||||||
|
|
||||||
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
|
||||||
|
@ -9024,6 +9069,108 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
|
||||||
|
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||||
|
let language = Arc::new(Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
brackets: vec![BracketPair {
|
||||||
|
start: "{".to_string(),
|
||||||
|
end: "}".to_string(),
|
||||||
|
close: true,
|
||||||
|
newline: true,
|
||||||
|
}],
|
||||||
|
autoclose_before: "}".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Some(tree_sitter_rust::language()),
|
||||||
|
));
|
||||||
|
|
||||||
|
let text = r#"
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
"#
|
||||||
|
.unindent();
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
|
||||||
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
|
||||||
|
editor
|
||||||
|
.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.select_ranges([
|
||||||
|
Point::new(0, 1)..Point::new(0, 1),
|
||||||
|
Point::new(1, 1)..Point::new(1, 1),
|
||||||
|
Point::new(2, 1)..Point::new(2, 1),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.handle_input(&Input("{".to_string()), cx);
|
||||||
|
editor.handle_input(&Input("{".to_string()), cx);
|
||||||
|
editor.handle_input(&Input("_".to_string()), cx);
|
||||||
|
assert_eq!(
|
||||||
|
editor.text(cx),
|
||||||
|
"
|
||||||
|
a{{_}}
|
||||||
|
b{{_}}
|
||||||
|
c{{_}}
|
||||||
|
"
|
||||||
|
.unindent()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
editor.selections.ranges::<Point>(cx),
|
||||||
|
[
|
||||||
|
Point::new(0, 4)..Point::new(0, 4),
|
||||||
|
Point::new(1, 4)..Point::new(1, 4),
|
||||||
|
Point::new(2, 4)..Point::new(2, 4)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.backspace(&Default::default(), cx);
|
||||||
|
editor.backspace(&Default::default(), cx);
|
||||||
|
assert_eq!(
|
||||||
|
editor.text(cx),
|
||||||
|
"
|
||||||
|
a{}
|
||||||
|
b{}
|
||||||
|
c{}
|
||||||
|
"
|
||||||
|
.unindent()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
editor.selections.ranges::<Point>(cx),
|
||||||
|
[
|
||||||
|
Point::new(0, 2)..Point::new(0, 2),
|
||||||
|
Point::new(1, 2)..Point::new(1, 2),
|
||||||
|
Point::new(2, 2)..Point::new(2, 2)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
editor.delete_to_previous_word_start(&Default::default(), cx);
|
||||||
|
assert_eq!(
|
||||||
|
editor.text(cx),
|
||||||
|
"
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
"
|
||||||
|
.unindent()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
editor.selections.ranges::<Point>(cx),
|
||||||
|
[
|
||||||
|
Point::new(0, 1)..Point::new(0, 1),
|
||||||
|
Point::new(1, 1)..Point::new(1, 1),
|
||||||
|
Point::new(2, 1)..Point::new(2, 1)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_snippets(cx: &mut gpui::TestAppContext) {
|
async fn test_snippets(cx: &mut gpui::TestAppContext) {
|
||||||
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
cx.update(|cx| cx.set_global(Settings::test(cx)));
|
||||||
|
|
Loading…
Reference in a new issue