Add support for auto surround (#13217)

![result](https://github.com/zed-industries/zed/assets/32017007/c400081f-be5d-48fa-994f-90a00e2be359)

In the past, Zed used a single switch called `autoclose` to control both
`autoclose` and `auto_surround` functionalities:
+ `autoclose`: when input '(', append ')' automatically.
+ `auto_surround`: when select text and input '(', surround text with
'(' and ')' automatically.

This PR separates `auto_surround` from `autoclose` to support `<`. 

Previously, if `autoclose` of `<` was set to `false`, `auto_surround`
couldn't be used. However, setting `autoclose` to `true` would affect
the default behavior of simple expression. For example, `a < b` would
become `a <> b`.

For more information, see #13187.

Fix #12898.

Release Notes:

- Added support for `auto_surround`
([#12898](https://github.com/zed-industries/zed/issues/12898)).
This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ 2024-06-20 17:48:46 +08:00 committed by GitHub
parent 963b0c010a
commit 95b06097ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 96 additions and 6 deletions

View file

@ -532,6 +532,7 @@ pub struct Editor {
next_editor_action_id: EditorActionId,
editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
use_autoclose: bool,
use_auto_surround: bool,
auto_replace_emoji_shortcode: bool,
show_git_blame_gutter: bool,
show_git_blame_inline: bool,
@ -1811,6 +1812,7 @@ impl Editor {
use_modal_editing: mode == EditorMode::Full,
read_only: false,
use_autoclose: true,
use_auto_surround: true,
auto_replace_emoji_shortcode: false,
leader_peer_id: None,
remote_id: None,
@ -2188,6 +2190,10 @@ impl Editor {
self.use_autoclose = autoclose;
}
pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
self.use_auto_surround = auto_surround;
}
pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
self.auto_replace_emoji_shortcode = auto_replace;
}
@ -2887,7 +2893,7 @@ impl Editor {
// `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
// and they are removing the character that triggered IME popup.
for (pair, enabled) in scope.brackets() {
if !pair.close {
if !pair.close && !pair.surround {
continue;
}
@ -2905,9 +2911,10 @@ impl Editor {
}
if let Some(bracket_pair) = bracket_pair {
let autoclose = self.use_autoclose
&& snapshot.settings_at(selection.start, cx).use_autoclose;
let snapshot_settings = snapshot.settings_at(selection.start, cx);
let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
let auto_surround =
self.use_auto_surround && snapshot_settings.use_auto_surround;
if selection.is_empty() {
if is_bracket_pair_start {
let prefix_len = bracket_pair.start.len() - text.len();
@ -2929,6 +2936,7 @@ impl Editor {
&bracket_pair.start[..prefix_len],
));
if autoclose
&& bracket_pair.close
&& following_text_allows_autoclose
&& preceding_text_matches_prefix
{
@ -2980,7 +2988,8 @@ impl Editor {
}
// If an opening bracket is 1 character long and is typed while
// text is selected, then surround that text with the bracket pair.
else if autoclose
else if auto_surround
&& bracket_pair.surround
&& is_bracket_pair_start
&& bracket_pair.start.chars().count() == 1
{
@ -12188,9 +12197,12 @@ impl ViewInputHandler for Editor {
// Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
let use_autoclose = this.use_autoclose;
let use_auto_surround = this.use_auto_surround;
this.set_use_autoclose(false);
this.set_use_auto_surround(false);
this.handle_input(text, cx);
this.set_use_autoclose(use_autoclose);
this.set_use_auto_surround(use_auto_surround);
if let Some(new_selected_range) = new_selected_range_utf16 {
let snapshot = this.buffer.read(cx).read(cx);

View file

@ -4635,12 +4635,14 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: false,
surround: false,
newline: true,
},
],
@ -4684,7 +4686,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
}
#[gpui::test]
async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
@ -4697,32 +4699,44 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/*".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "\"".to_string(),
end: "\"".to_string(),
close: true,
surround: true,
newline: false,
},
BracketPair {
start: "<".to_string(),
end: ">".to_string(),
close: false,
surround: true,
newline: true,
},
],
..Default::default()
},
@ -4850,6 +4864,16 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
cx.assert_editor_state("a\"ˇ\"");
cx.update_editor(|view, cx| view.handle_input("\"", cx));
cx.assert_editor_state("a\"\"ˇ");
// Don't autoclose pair if autoclose is disabled
cx.set_state("ˇ");
cx.update_editor(|view, cx| view.handle_input("<", cx));
cx.assert_editor_state("");
// Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
cx.set_state("«aˇ» b");
cx.update_editor(|view, cx| view.handle_input("<", cx));
cx.assert_editor_state("<«aˇ»> b");
}
#[gpui::test]
@ -4868,18 +4892,21 @@ async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestA
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: false,
newline: true,
},
],
@ -5293,12 +5320,14 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/* ".to_string(),
end: "*/".to_string(),
close: true,
surround: true,
..Default::default()
},
],
@ -5447,6 +5476,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
}],
..Default::default()
@ -5558,18 +5588,21 @@ async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppC
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "[".to_string(),
end: "]".to_string(),
close: false,
surround: true,
newline: true,
},
],
@ -7537,12 +7570,14 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
},
BracketPair {
start: "/* ".to_string(),
end: " */".to_string(),
close: true,
surround: true,
newline: true,
},
],
@ -8344,6 +8379,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
}],
disabled_scopes_by_bracket_ix: Vec::new(),

View file

@ -55,12 +55,14 @@ mod tests {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: false,
surround: false,
newline: true,
},
],

View file

@ -181,6 +181,7 @@ impl EditorLspTestContext {
start: "{".to_string(),
end: "}".to_string(),
close: true,
surround: true,
newline: true,
}],
disabled_scopes_by_bracket_ix: Default::default(),