Merge pull request #1692 from zed-industries/avoid-duplicate-autoformat-edits

Max Brunsfeld created

Avoid duplicate autoformat edits

Change summary

crates/editor/src/editor_tests.rs | 759 ++++++++++++++++----------------
crates/gpui/src/executor.rs       |   7 
crates/project/src/project.rs     |  33 +
3 files changed, 411 insertions(+), 388 deletions(-)

Detailed changes

crates/editor/src/editor_tests.rs 🔗

@@ -1,9 +1,8 @@
+use super::*;
 use crate::test::{
     assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext,
     EditorTestContext,
 };
-
-use super::*;
 use futures::StreamExt;
 use gpui::{
     geometry::rect::RectF,
@@ -418,12 +417,12 @@ fn test_canceling_pending_selection(cx: &mut gpui::MutableAppContext) {
 fn test_clone(cx: &mut gpui::MutableAppContext) {
     let (text, selection_ranges) = marked_text_ranges(
         indoc! {"
-                one
-                two
-                threeˇ
-                four
-                fiveˇ
-            "},
+            one
+            two
+            threeˇ
+            four
+            fiveˇ
+        "},
         true,
     );
     cx.set_global(Settings::test(cx));
@@ -624,22 +623,22 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
     cx.set_global(Settings::test(cx));
     let buffer = MultiBuffer::build_simple(
         &"
-                impl Foo {
-                    // Hello!
+            impl Foo {
+                // Hello!
 
-                    fn a() {
-                        1
-                    }
+                fn a() {
+                    1
+                }
 
-                    fn b() {
-                        2
-                    }
+                fn b() {
+                    2
+                }
 
-                    fn c() {
-                        3
-                    }
+                fn c() {
+                    3
                 }
-            "
+            }
+        "
         .unindent(),
         cx,
     );
@@ -653,20 +652,20 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
         assert_eq!(
             view.display_text(cx),
             "
-                    impl Foo {
-                        // Hello!
+                impl Foo {
+                    // Hello!
 
-                        fn a() {
-                            1
-                        }
+                    fn a() {
+                        1
+                    }
 
-                        fn b() {…
-                        }
+                    fn b() {…
+                    }
 
-                        fn c() {…
-                        }
+                    fn c() {…
                     }
-                "
+                }
+            "
             .unindent(),
         );
 
@@ -674,9 +673,9 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
         assert_eq!(
             view.display_text(cx),
             "
-                    impl Foo {…
-                    }
-                "
+                impl Foo {…
+                }
+            "
             .unindent(),
         );
 
@@ -684,20 +683,20 @@ fn test_fold(cx: &mut gpui::MutableAppContext) {
         assert_eq!(
             view.display_text(cx),
             "
-                    impl Foo {
-                        // Hello!
+                impl Foo {
+                    // Hello!
 
-                        fn a() {
-                            1
-                        }
+                    fn a() {
+                        1
+                    }
 
-                        fn b() {…
-                        }
+                    fn b() {…
+                    }
 
-                        fn c() {…
-                        }
+                    fn c() {…
                     }
-                "
+                }
+            "
             .unindent(),
         );
 
@@ -1264,14 +1263,14 @@ fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
     cx.set_global(Settings::test(cx));
     let buffer = MultiBuffer::build_simple(
         "
-                a
-                b(
-                    X
-                )
-                c(
-                    X
-                )
-            "
+            a
+            b(
+                X
+            )
+            c(
+                X
+            )
+        "
         .unindent()
         .as_str(),
         cx,
@@ -1301,10 +1300,10 @@ fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
         assert_eq!(
             buffer.read(cx).text(),
             "
-                    a
-                    b()
-                    c()
-                "
+                a
+                b()
+                c()
+            "
             .unindent()
         );
     });
@@ -1322,12 +1321,12 @@ fn test_newline_with_old_selections(cx: &mut gpui::MutableAppContext) {
         assert_eq!(
             editor.text(cx),
             "
-                    a
-                    b(
-                    )
-                    c(
-                    )
-                "
+                a
+                b(
+                )
+                c(
+                )
+            "
             .unindent()
         );
 
@@ -1362,33 +1361,33 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
     cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 
     cx.set_state(indoc! {"
-            const a: ˇA = (
-                (ˇ
-                    «const_functionˇ»(ˇ),
-                    so«mˇ»et«hˇ»ing_ˇelse,ˇ
-                )ˇ
-            ˇ);ˇ
-        "});
+        const a: ˇA = (
+            (ˇ
+                «const_functionˇ»(ˇ),
+                so«mˇ»et«hˇ»ing_ˇelse,ˇ
+            )ˇ
+        ˇ);ˇ
+    "});
     cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
     cx.assert_editor_state(indoc! {"
-            const a: A = (
+        const a: A = (
+            ˇ
+            (
                 ˇ
-                (
-                    ˇ
-                    const_function(),
-                    ˇ
-                    ˇ
-                    something_else,
-                    ˇ
-                    ˇ
-                    ˇ
-                    ˇ
-                )
+                const_function(),
                 ˇ
-            );
-            ˇ
+                ˇ
+                something_else,
+                ˇ
+                ˇ
+                ˇ
+                ˇ
+            )
             ˇ
-        "});
+        );
+        ˇ
+        ˇ
+    "});
 }
 
 #[gpui::test]
@@ -1427,26 +1426,26 @@ async fn test_tab(cx: &mut gpui::TestAppContext) {
         });
     });
     cx.set_state(indoc! {"
-            ˇabˇc
-            ˇ🏀ˇ🏀ˇefg
-            dˇ
-        "});
+        ˇabˇc
+        ˇ🏀ˇ🏀ˇefg
+        dˇ
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-              ˇab ˇc
-              ˇ🏀  ˇ🏀  ˇefg
-           d  ˇ
-        "});
+           ˇab ˇc
+           ˇ🏀  ˇ🏀  ˇefg
+        d  ˇ
+    "});
 
     cx.set_state(indoc! {"
-            a
-            «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
-        "});
+        a
+        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            a
-               «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
-        "});
+        a
+           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
+    "});
 }
 
 #[gpui::test]
@@ -1466,45 +1465,45 @@ async fn test_tab_on_blank_line_auto_indents(cx: &mut gpui::TestAppContext) {
     // a soft tab. cursors that are to the left of the suggested indent
     // auto-indent their line.
     cx.set_state(indoc! {"
-            ˇ
-            const a: B = (
-                c(
-                    d(
-            ˇ
-                    )
-            ˇ
-            ˇ    )
-            );
-        "});
+        ˇ
+        const a: B = (
+            c(
+                d(
+        ˇ
+                )
+        ˇ
+        ˇ    )
+        );
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-                ˇ
-            const a: B = (
-                c(
-                    d(
-                        ˇ
-                    )
+            ˇ
+        const a: B = (
+            c(
+                d(
                     ˇ
-                ˇ)
-            );
-        "});
+                )
+                ˇ
+            ˇ)
+        );
+    "});
 
     // handle auto-indent when there are multiple cursors on the same line
     cx.set_state(indoc! {"
-            const a: B = (
-                c(
-            ˇ    ˇ    
-            ˇ    )
-            );
-        "});
+        const a: B = (
+            c(
+        ˇ    ˇ
+        ˇ    )
+        );
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(
-                    ˇ
-                ˇ)
-            );
-        "});
+        const a: B = (
+            c(
+                ˇ
+            ˇ)
+        );
+    "});
 }
 
 #[gpui::test]
@@ -1512,68 +1511,68 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
     let mut cx = EditorTestContext::new(cx);
 
     cx.set_state(indoc! {"
-              «oneˇ» «twoˇ»
-            three
-             four
-        "});
+          «oneˇ» «twoˇ»
+        three
+         four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-                «oneˇ» «twoˇ»
-            three
-             four
-        "});
+            «oneˇ» «twoˇ»
+        three
+         four
+    "});
 
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            «oneˇ» «twoˇ»
-            three
-             four
-        "});
+        «oneˇ» «twoˇ»
+        three
+         four
+    "});
 
     // select across line ending
     cx.set_state(indoc! {"
-            one two
-            t«hree
-            ˇ» four
-        "});
+        one two
+        t«hree
+        ˇ» four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-                t«hree
-            ˇ» four
-        "});
+        one two
+            t«hree
+        ˇ» four
+    "});
 
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            t«hree
-            ˇ» four
-        "});
+        one two
+        t«hree
+        ˇ» four
+    "});
 
     // Ensure that indenting/outdenting works when the cursor is at column 0.
     cx.set_state(indoc! {"
-            one two
-            ˇthree
-                four
-        "});
+        one two
+        ˇthree
+            four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-                ˇthree
-                four
-        "});
+        one two
+            ˇthree
+            four
+    "});
 
     cx.set_state(indoc! {"
-            one two
-            ˇ    three
-             four
-        "});
+        one two
+        ˇ    three
+            four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            ˇthree
-             four
-        "});
+        one two
+        ˇthree
+            four
+    "});
 }
 
 #[gpui::test]
@@ -1587,90 +1586,90 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
 
     // select two ranges on one line
     cx.set_state(indoc! {"
-            «oneˇ» «twoˇ»
-            three
-            four
-        "});
+        «oneˇ» «twoˇ»
+        three
+        four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            \t«oneˇ» «twoˇ»
-            three
-            four
-        "});
+        \t«oneˇ» «twoˇ»
+        three
+        four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            \t\t«oneˇ» «twoˇ»
-            three
-            four
-        "});
+        \t\t«oneˇ» «twoˇ»
+        three
+        four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            \t«oneˇ» «twoˇ»
-            three
-            four
-        "});
+        \t«oneˇ» «twoˇ»
+        three
+        four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            «oneˇ» «twoˇ»
-            three
-            four
-        "});
+        «oneˇ» «twoˇ»
+        three
+        four
+    "});
 
     // select across a line ending
     cx.set_state(indoc! {"
-            one two
-            t«hree
-            ˇ»four
-        "});
+        one two
+        t«hree
+        ˇ»four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            \tt«hree
-            ˇ»four
-        "});
+        one two
+        \tt«hree
+        ˇ»four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            \t\tt«hree
-            ˇ»four
-        "});
+        one two
+        \t\tt«hree
+        ˇ»four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            \tt«hree
-            ˇ»four
-        "});
+        one two
+        \tt«hree
+        ˇ»four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            t«hree
-            ˇ»four
-        "});
+        one two
+        t«hree
+        ˇ»four
+    "});
 
     // Ensure that indenting/outdenting works when the cursor is at column 0.
     cx.set_state(indoc! {"
-            one two
-            ˇthree
-            four
-        "});
+        one two
+        ˇthree
+        four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            ˇthree
-            four
-        "});
+        one two
+        ˇthree
+        four
+    "});
     cx.update_editor(|e, cx| e.tab(&Tab, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            \tˇthree
-            four
-        "});
+        one two
+        \tˇthree
+        four
+    "});
     cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
     cx.assert_editor_state(indoc! {"
-            one two
-            ˇthree
-            four
-        "});
+        one two
+        ˇthree
+        four
+    "});
 }
 
 #[gpui::test]
@@ -1739,21 +1738,21 @@ fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
         assert_eq!(
             editor.text(cx),
             indoc! {"
-                    a = 1
-                    b = 2
+                a = 1
+                b = 2
 
-                    const c: usize = 3;
-                "}
+                const c: usize = 3;
+            "}
         );
 
         select_ranges(
             &mut editor,
             indoc! {"
-                    «aˇ» = 1
-                    b = 2
+                «aˇ» = 1
+                b = 2
 
-                    «const c:ˇ» usize = 3;
-                "},
+                «const c:ˇ» usize = 3;
+            "},
             cx,
         );
 
@@ -1761,22 +1760,22 @@ fn test_indent_outdent_with_excerpts(cx: &mut gpui::MutableAppContext) {
         assert_text_with_selections(
             &mut editor,
             indoc! {"
-                      «aˇ» = 1
-                    b = 2
+                  «aˇ» = 1
+                b = 2
 
-                        «const c:ˇ» usize = 3;
-                "},
+                    «const c:ˇ» usize = 3;
+            "},
             cx,
         );
         editor.tab_prev(&TabPrev, cx);
         assert_text_with_selections(
             &mut editor,
             indoc! {"
-                    «aˇ» = 1
-                    b = 2
+                «aˇ» = 1
+                b = 2
 
-                    «const c:ˇ» usize = 3;
-                "},
+                «const c:ˇ» usize = 3;
+            "},
             cx,
         );
 
@@ -1790,45 +1789,45 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) {
 
     // Basic backspace
     cx.set_state(indoc! {"
-            onˇe two three
-            fou«rˇ» five six
-            seven «ˇeight nine
-            »ten
-        "});
+        onˇe two three
+        fou«rˇ» five six
+        seven «ˇeight nine
+        »ten
+    "});
     cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
     cx.assert_editor_state(indoc! {"
-            oˇe two three
-            fouˇ five six
-            seven ˇten
-        "});
+        oˇe two three
+        fouˇ five six
+        seven ˇten
+    "});
 
     // Test backspace inside and around indents
     cx.set_state(indoc! {"
-            zero
-                ˇone
-                    ˇtwo
-                ˇ ˇ ˇ  three
-            ˇ  ˇ  four
-        "});
-    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
-    cx.assert_editor_state(indoc! {"
-            zero
+        zero
             ˇone
                 ˇtwo
-            ˇ  threeˇ  four
-        "});
+            ˇ ˇ ˇ  three
+        ˇ  ˇ  four
+    "});
+    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
+    cx.assert_editor_state(indoc! {"
+        zero
+        ˇone
+            ˇtwo
+        ˇ  threeˇ  four
+    "});
 
     // Test backspace with line_mode set to true
     cx.update_editor(|e, _| e.selections.line_mode = true);
     cx.set_state(indoc! {"
-            The ˇquick ˇbrown
-            fox jumps over
-            the lazy dog
-            ˇThe qu«ick bˇ»rown"});
+        The ˇquick ˇbrown
+        fox jumps over
+        the lazy dog
+        ˇThe qu«ick bˇ»rown"});
     cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
     cx.assert_editor_state(indoc! {"
-            ˇfox jumps over
-            the lazy dogˇ"});
+        ˇfox jumps over
+        the lazy dogˇ"});
 }
 
 #[gpui::test]
@@ -1836,25 +1835,25 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
     let mut cx = EditorTestContext::new(cx);
 
     cx.set_state(indoc! {"
-            onˇe two three
-            fou«rˇ» five six
-            seven «ˇeight nine
-            »ten
-        "});
+        onˇe two three
+        fou«rˇ» five six
+        seven «ˇeight nine
+        »ten
+    "});
     cx.update_editor(|e, cx| e.delete(&Delete, cx));
     cx.assert_editor_state(indoc! {"
-            onˇ two three
-            fouˇ five six
-            seven ˇten
-        "});
+        onˇ two three
+        fouˇ five six
+        seven ˇten
+    "});
 
     // Test backspace with line_mode set to true
     cx.update_editor(|e, _| e.selections.line_mode = true);
     cx.set_state(indoc! {"
-            The ˇquick ˇbrown
-            fox «ˇjum»ps over
-            the lazy dog
-            ˇThe qu«ick bˇ»rown"});
+        The ˇquick ˇbrown
+        fox «ˇjum»ps over
+        the lazy dog
+        ˇThe qu«ick bˇ»rown"});
     cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
     cx.assert_editor_state("ˇthe lazy dogˇ");
 }
@@ -2191,57 +2190,57 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
         e.handle_input(") ", cx);
     });
     cx.assert_editor_state(indoc! {"
-            ( one✅ 
-            three 
-            five ) ˇtwo one✅ four three six five ( one✅ 
-            three 
-            five ) ˇ"});
+        ( one✅ 
+        three 
+        five ) ˇtwo one✅ four three six five ( one✅ 
+        three 
+        five ) ˇ"});
 
     // Cut with three selections, one of which is full-line.
     cx.set_state(indoc! {"
-            1«2ˇ»3
-            4ˇ567
-            «8ˇ»9"});
+        1«2ˇ»3
+        4ˇ567
+        «8ˇ»9"});
     cx.update_editor(|e, cx| e.cut(&Cut, cx));
     cx.assert_editor_state(indoc! {"
-            1ˇ3
-            ˇ9"});
+        1ˇ3
+        ˇ9"});
 
     // Paste with three selections, noticing how the copied selection that was full-line
     // gets inserted before the second cursor.
     cx.set_state(indoc! {"
-            1ˇ3
-            9ˇ
-            «oˇ»ne"});
+        1ˇ3
+        9ˇ
+        «oˇ»ne"});
     cx.update_editor(|e, cx| e.paste(&Paste, cx));
     cx.assert_editor_state(indoc! {"
-            12ˇ3
-            4567
-            9ˇ
-            8ˇne"});
+        12ˇ3
+        4567
+        9ˇ
+        8ˇne"});
 
     // Copy with a single cursor only, which writes the whole line into the clipboard.
     cx.set_state(indoc! {"
-            The quick brown
-            fox juˇmps over
-            the lazy dog"});
+        The quick brown
+        fox juˇmps over
+        the lazy dog"});
     cx.update_editor(|e, cx| e.copy(&Copy, cx));
     cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
 
     // Paste with three selections, noticing how the copied full-line selection is inserted
     // before the empty selections but replaces the selection that is non-empty.
     cx.set_state(indoc! {"
-            Tˇhe quick brown
-            «foˇ»x jumps over
-            tˇhe lazy dog"});
+        Tˇhe quick brown
+        «foˇ»x jumps over
+        tˇhe lazy dog"});
     cx.update_editor(|e, cx| e.paste(&Paste, cx));
     cx.assert_editor_state(indoc! {"
-            fox jumps over
-            Tˇhe quick brown
-            fox jumps over
-            ˇx jumps over
-            fox jumps over
-            tˇhe lazy dog"});
+        fox jumps over
+        Tˇhe quick brown
+        fox jumps over
+        ˇx jumps over
+        fox jumps over
+        tˇhe lazy dog"});
 }
 
 #[gpui::test]
@@ -2255,105 +2254,105 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 
     // Cut an indented block, without the leading whitespace.
     cx.set_state(indoc! {"
-            const a: B = (
-                c(),
-                «d(
-                    e,
-                    f
-                )ˇ»
-            );
-        "});
+        const a: B = (
+            c(),
+            «d(
+                e,
+                f
+            )ˇ»
+        );
+    "});
     cx.update_editor(|e, cx| e.cut(&Cut, cx));
     cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                ˇ
-            );
-        "});
+        const a: B = (
+            c(),
+            ˇ
+        );
+    "});
 
     // Paste it at the same position.
     cx.update_editor(|e, cx| e.paste(&Paste, cx));
     cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    f
-                )ˇ
-            );
-        "});
+        const a: B = (
+            c(),
+            d(
+                e,
+                f
+            )ˇ
+        );
+    "});
 
     // Paste it at a line with a lower indent level.
     cx.set_state(indoc! {"
-            ˇ
-            const a: B = (
-                c(),
-            );
-        "});
+        ˇ
+        const a: B = (
+            c(),
+        );
+    "});
     cx.update_editor(|e, cx| e.paste(&Paste, cx));
     cx.assert_editor_state(indoc! {"
-            d(
-                e,
-                f
-            )ˇ
-            const a: B = (
-                c(),
-            );
-        "});
+        d(
+            e,
+            f
+        )ˇ
+        const a: B = (
+            c(),
+        );
+    "});
 
     // Cut an indented block, with the leading whitespace.
     cx.set_state(indoc! {"
-            const a: B = (
-                c(),
-            «    d(
-                    e,
-                    f
-                )
-            ˇ»);
-        "});
+        const a: B = (
+            c(),
+        «    d(
+                e,
+                f
+            )
+        ˇ»);
+    "});
     cx.update_editor(|e, cx| e.cut(&Cut, cx));
     cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-            ˇ);
-        "});
+        const a: B = (
+            c(),
+        ˇ);
+    "});
 
     // Paste it at the same position.
     cx.update_editor(|e, cx| e.paste(&Paste, cx));
     cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    f
-                )
-            ˇ);
-        "});
+        const a: B = (
+            c(),
+            d(
+                e,
+                f
+            )
+        ˇ);
+    "});
 
     // Paste it at a line with a higher indent level.
     cx.set_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
-                    e,
-                    fˇ
-                )
-            );
-        "});
+        const a: B = (
+            c(),
+            d(
+                e,
+                fˇ
+            )
+        );
+    "});
     cx.update_editor(|e, cx| e.paste(&Paste, cx));
     cx.assert_editor_state(indoc! {"
-            const a: B = (
-                c(),
-                d(
+        const a: B = (
+            c(),
+            d(
+                e,
+                f    d(
                     e,
-                    f    d(
-                        e,
-                        f
-                    )
-            ˇ
+                    f
                 )
-            );
-        "});
+        ˇ
+            )
+        );
+    "});
 }
 
 #[gpui::test]

crates/gpui/src/executor.rs 🔗

@@ -325,7 +325,12 @@ impl Deterministic {
         let mut state = self.state.lock();
         let wakeup_at = state.now + duration;
         let id = util::post_inc(&mut state.next_timer_id);
-        state.pending_timers.push((id, wakeup_at, tx));
+        match state
+            .pending_timers
+            .binary_search_by_key(&wakeup_at, |e| e.1)
+        {
+            Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)),
+        }
         let state = self.state.clone();
         Timer::Deterministic(DeterministicTimer { rx, id, state })
     }

crates/project/src/project.rs 🔗

@@ -8,10 +8,7 @@ pub mod worktree;
 mod project_tests;
 
 use anyhow::{anyhow, Context, Result};
-use client::{
-    proto::{self},
-    Client, PeerId, TypedEnvelope, User, UserStore,
-};
+use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
 use clock::ReplicaId;
 use collections::{hash_map, BTreeMap, HashMap, HashSet};
 use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt};
@@ -66,7 +63,7 @@ use std::{
     time::Instant,
 };
 use thiserror::Error;
-use util::{post_inc, ResultExt, TryFutureExt as _};
+use util::{defer, post_inc, ResultExt, TryFutureExt as _};
 
 pub use db::Db;
 pub use fs::*;
@@ -128,6 +125,7 @@ pub struct Project {
     opened_buffers: HashMap<u64, OpenBuffer>,
     incomplete_buffers: HashMap<u64, ModelHandle<Buffer>>,
     buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
+    buffers_being_formatted: HashSet<usize>,
     nonce: u128,
     initialized_persistent_state: bool,
     _maintain_buffer_languages: Task<()>,
@@ -512,6 +510,7 @@ impl Project {
                 language_server_statuses: Default::default(),
                 last_workspace_edits_by_language_server: Default::default(),
                 language_server_settings: Default::default(),
+                buffers_being_formatted: Default::default(),
                 next_language_server_id: 0,
                 nonce: StdRng::from_entropy().gen(),
                 initialized_persistent_state: false,
@@ -627,6 +626,7 @@ impl Project {
                 last_workspace_edits_by_language_server: Default::default(),
                 next_language_server_id: 0,
                 opened_buffers: Default::default(),
+                buffers_being_formatted: Default::default(),
                 buffer_snapshots: Default::default(),
                 nonce: StdRng::from_entropy().gen(),
                 initialized_persistent_state: false,
@@ -3113,7 +3113,26 @@ impl Project {
                     .await?;
             }
 
-            for (buffer, buffer_abs_path, language_server) in local_buffers {
+            // Do not allow multiple concurrent formatting requests for the
+            // same buffer.
+            this.update(&mut cx, |this, _| {
+                local_buffers
+                    .retain(|(buffer, _, _)| this.buffers_being_formatted.insert(buffer.id()));
+            });
+            let _cleanup = defer({
+                let this = this.clone();
+                let mut cx = cx.clone();
+                let local_buffers = &local_buffers;
+                move || {
+                    this.update(&mut cx, |this, _| {
+                        for (buffer, _, _) in local_buffers {
+                            this.buffers_being_formatted.remove(&buffer.id());
+                        }
+                    });
+                }
+            });
+
+            for (buffer, buffer_abs_path, language_server) in &local_buffers {
                 let (format_on_save, formatter, tab_size) = buffer.read_with(&cx, |buffer, cx| {
                     let settings = cx.global::<Settings>();
                     let language_name = buffer.language().map(|language| language.name());
@@ -3165,7 +3184,7 @@ impl Project {
                             buffer.forget_transaction(transaction.id)
                         });
                     }
-                    project_transaction.0.insert(buffer, transaction);
+                    project_transaction.0.insert(buffer.clone(), transaction);
                 }
             }