@@ -4394,10 +4394,50 @@ impl Editor {
&& bracket_pair.start.len() == 1
{
let target = bracket_pair.start.chars().next().unwrap();
+ let mut byte_offset = 0u32;
let current_line_count = snapshot
.reversed_chars_at(selection.start)
.take_while(|&c| c != '\n')
- .filter(|&c| c == target)
+ .filter(|c| {
+ byte_offset += c.len_utf8() as u32;
+ if *c != target {
+ return false;
+ }
+
+ let point = Point::new(
+ selection.start.row,
+ selection.start.column.saturating_sub(byte_offset),
+ );
+
+ let is_enabled = snapshot
+ .language_scope_at(point)
+ .and_then(|scope| {
+ scope
+ .brackets()
+ .find(|(pair, _)| {
+ pair.start == bracket_pair.start
+ })
+ .map(|(_, enabled)| enabled)
+ })
+ .unwrap_or(true);
+
+ let is_delimiter = snapshot
+ .language_scope_at(Point::new(
+ point.row,
+ point.column + 1,
+ ))
+ .and_then(|scope| {
+ scope
+ .brackets()
+ .find(|(pair, _)| {
+ pair.start == bracket_pair.start
+ })
+ .map(|(_, enabled)| !enabled)
+ })
+ .unwrap_or(false);
+
+ is_enabled && !is_delimiter
+ })
.count();
current_line_count % 2 == 1
} else {
@@ -10869,6 +10869,115 @@ async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
);
}
+#[gpui::test]
+async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+ let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
+
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+ // Double quote inside single-quoted string
+ cx.set_state(indoc! {r#"
+ def main():
+ items = ['"', ˇ]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("\"", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ['"', "ˇ"]
+ "#});
+
+ // Two double quotes inside single-quoted string
+ cx.set_state(indoc! {r#"
+ def main():
+ items = ['""', ˇ]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("\"", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ['""', "ˇ"]
+ "#});
+
+ // Single quote inside double-quoted string
+ cx.set_state(indoc! {r#"
+ def main():
+ items = ["'", ˇ]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("'", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ["'", 'ˇ']
+ "#});
+
+ // Two single quotes inside double-quoted string
+ cx.set_state(indoc! {r#"
+ def main():
+ items = ["''", ˇ]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("'", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ["''", 'ˇ']
+ "#});
+
+ // Mixed quotes on same line
+ cx.set_state(indoc! {r#"
+ def main():
+ items = ['"""', "'''''", ˇ]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("\"", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ['"""', "'''''", "ˇ"]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.move_right(&MoveRight, window, cx);
+ });
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input(", ", window, cx);
+ });
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("'", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ['"""', "'''''", "", 'ˇ']
+ "#});
+}
+
+#[gpui::test]
+async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
+ init_test(cx, |_| {});
+
+ let mut cx = EditorTestContext::new(cx).await;
+ let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
+ cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
+
+ cx.set_state(indoc! {r#"
+ def main():
+ items = ["🎉", ˇ]
+ "#});
+ cx.update_editor(|editor, window, cx| {
+ editor.handle_input("\"", window, cx);
+ });
+ cx.assert_editor_state(indoc! {r#"
+ def main():
+ items = ["🎉", "ˇ"]
+ "#});
+}
+
#[gpui::test]
async fn test_surround_with_pair(cx: &mut TestAppContext) {
init_test(cx, |_| {});