editor_tests.rs

    1use super::*;
    2use crate::{
    3    scroll::scroll_amount::ScrollAmount,
    4    test::{
    5        assert_text_with_selections, build_editor, editor_hunks,
    6        editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
    7        expanded_hunks, expanded_hunks_background_highlights, select_ranges,
    8    },
    9    JoinLines,
   10};
   11use futures::StreamExt;
   12use gpui::{div, TestAppContext, VisualTestContext, WindowOptions};
   13use indoc::indoc;
   14use language::{
   15    language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
   16    BracketPairConfig,
   17    Capability::ReadWrite,
   18    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override, Point,
   19};
   20use parking_lot::Mutex;
   21use project::project_settings::{LspSettings, ProjectSettings};
   22use project::FakeFs;
   23use serde_json::{self, json};
   24use std::sync::atomic;
   25use std::sync::atomic::AtomicUsize;
   26use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
   27use unindent::Unindent;
   28use util::{
   29    assert_set_eq,
   30    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
   31};
   32use workspace::{
   33    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   34    NavigationEntry, ViewId,
   35};
   36
   37#[gpui::test]
   38fn test_edit_events(cx: &mut TestAppContext) {
   39    init_test(cx, |_| {});
   40
   41    let buffer = cx.new_model(|cx| {
   42        let mut buffer = language::Buffer::local("123456", cx);
   43        buffer.set_group_interval(Duration::from_secs(1));
   44        buffer
   45    });
   46
   47    let events = Rc::new(RefCell::new(Vec::new()));
   48    let editor1 = cx.add_window({
   49        let events = events.clone();
   50        |cx| {
   51            let view = cx.view().clone();
   52            cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
   53                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
   54                    events.borrow_mut().push(("editor1", event.clone()));
   55                }
   56            })
   57            .detach();
   58            Editor::for_buffer(buffer.clone(), None, cx)
   59        }
   60    });
   61
   62    let editor2 = cx.add_window({
   63        let events = events.clone();
   64        |cx| {
   65            cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
   66                if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
   67                    events.borrow_mut().push(("editor2", event.clone()));
   68                }
   69            })
   70            .detach();
   71            Editor::for_buffer(buffer.clone(), None, cx)
   72        }
   73    });
   74
   75    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
   76
   77    // Mutating editor 1 will emit an `Edited` event only for that editor.
   78    _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
   79    assert_eq!(
   80        mem::take(&mut *events.borrow_mut()),
   81        [
   82            ("editor1", EditorEvent::Edited),
   83            ("editor1", EditorEvent::BufferEdited),
   84            ("editor2", EditorEvent::BufferEdited),
   85        ]
   86    );
   87
   88    // Mutating editor 2 will emit an `Edited` event only for that editor.
   89    _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
   90    assert_eq!(
   91        mem::take(&mut *events.borrow_mut()),
   92        [
   93            ("editor2", EditorEvent::Edited),
   94            ("editor1", EditorEvent::BufferEdited),
   95            ("editor2", EditorEvent::BufferEdited),
   96        ]
   97    );
   98
   99    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  100    _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
  101    assert_eq!(
  102        mem::take(&mut *events.borrow_mut()),
  103        [
  104            ("editor1", EditorEvent::Edited),
  105            ("editor1", EditorEvent::BufferEdited),
  106            ("editor2", EditorEvent::BufferEdited),
  107        ]
  108    );
  109
  110    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  111    _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
  112    assert_eq!(
  113        mem::take(&mut *events.borrow_mut()),
  114        [
  115            ("editor1", EditorEvent::Edited),
  116            ("editor1", EditorEvent::BufferEdited),
  117            ("editor2", EditorEvent::BufferEdited),
  118        ]
  119    );
  120
  121    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  122    _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
  123    assert_eq!(
  124        mem::take(&mut *events.borrow_mut()),
  125        [
  126            ("editor2", EditorEvent::Edited),
  127            ("editor1", EditorEvent::BufferEdited),
  128            ("editor2", EditorEvent::BufferEdited),
  129        ]
  130    );
  131
  132    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  133    _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
  134    assert_eq!(
  135        mem::take(&mut *events.borrow_mut()),
  136        [
  137            ("editor2", EditorEvent::Edited),
  138            ("editor1", EditorEvent::BufferEdited),
  139            ("editor2", EditorEvent::BufferEdited),
  140        ]
  141    );
  142
  143    // No event is emitted when the mutation is a no-op.
  144    _ = editor2.update(cx, |editor, cx| {
  145        editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
  146
  147        editor.backspace(&Backspace, cx);
  148    });
  149    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  150}
  151
  152#[gpui::test]
  153fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  154    init_test(cx, |_| {});
  155
  156    let mut now = Instant::now();
  157    let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
  158    let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
  159    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
  160    let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
  161
  162    _ = editor.update(cx, |editor, cx| {
  163        editor.start_transaction_at(now, cx);
  164        editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
  165
  166        editor.insert("cd", cx);
  167        editor.end_transaction_at(now, cx);
  168        assert_eq!(editor.text(cx), "12cd56");
  169        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  170
  171        editor.start_transaction_at(now, cx);
  172        editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
  173        editor.insert("e", cx);
  174        editor.end_transaction_at(now, cx);
  175        assert_eq!(editor.text(cx), "12cde6");
  176        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  177
  178        now += group_interval + Duration::from_millis(1);
  179        editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
  180
  181        // Simulate an edit in another editor
  182        _ = buffer.update(cx, |buffer, cx| {
  183            buffer.start_transaction_at(now, cx);
  184            buffer.edit([(0..1, "a")], None, cx);
  185            buffer.edit([(1..1, "b")], None, cx);
  186            buffer.end_transaction_at(now, cx);
  187        });
  188
  189        assert_eq!(editor.text(cx), "ab2cde6");
  190        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  191
  192        // Last transaction happened past the group interval in a different editor.
  193        // Undo it individually and don't restore selections.
  194        editor.undo(&Undo, cx);
  195        assert_eq!(editor.text(cx), "12cde6");
  196        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  197
  198        // First two transactions happened within the group interval in this editor.
  199        // Undo them together and restore selections.
  200        editor.undo(&Undo, cx);
  201        editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
  202        assert_eq!(editor.text(cx), "123456");
  203        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  204
  205        // Redo the first two transactions together.
  206        editor.redo(&Redo, cx);
  207        assert_eq!(editor.text(cx), "12cde6");
  208        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  209
  210        // Redo the last transaction on its own.
  211        editor.redo(&Redo, cx);
  212        assert_eq!(editor.text(cx), "ab2cde6");
  213        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  214
  215        // Test empty transactions.
  216        editor.start_transaction_at(now, cx);
  217        editor.end_transaction_at(now, cx);
  218        editor.undo(&Undo, cx);
  219        assert_eq!(editor.text(cx), "12cde6");
  220    });
  221}
  222
  223#[gpui::test]
  224fn test_ime_composition(cx: &mut TestAppContext) {
  225    init_test(cx, |_| {});
  226
  227    let buffer = cx.new_model(|cx| {
  228        let mut buffer = language::Buffer::local("abcde", cx);
  229        // Ensure automatic grouping doesn't occur.
  230        buffer.set_group_interval(Duration::ZERO);
  231        buffer
  232    });
  233
  234    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
  235    cx.add_window(|cx| {
  236        let mut editor = build_editor(buffer.clone(), cx);
  237
  238        // Start a new IME composition.
  239        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
  240        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
  241        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
  242        assert_eq!(editor.text(cx), "äbcde");
  243        assert_eq!(
  244            editor.marked_text_ranges(cx),
  245            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  246        );
  247
  248        // Finalize IME composition.
  249        editor.replace_text_in_range(None, "ā", cx);
  250        assert_eq!(editor.text(cx), "ābcde");
  251        assert_eq!(editor.marked_text_ranges(cx), None);
  252
  253        // IME composition edits are grouped and are undone/redone at once.
  254        editor.undo(&Default::default(), cx);
  255        assert_eq!(editor.text(cx), "abcde");
  256        assert_eq!(editor.marked_text_ranges(cx), None);
  257        editor.redo(&Default::default(), cx);
  258        assert_eq!(editor.text(cx), "ābcde");
  259        assert_eq!(editor.marked_text_ranges(cx), None);
  260
  261        // Start a new IME composition.
  262        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
  263        assert_eq!(
  264            editor.marked_text_ranges(cx),
  265            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  266        );
  267
  268        // Undoing during an IME composition cancels it.
  269        editor.undo(&Default::default(), cx);
  270        assert_eq!(editor.text(cx), "ābcde");
  271        assert_eq!(editor.marked_text_ranges(cx), None);
  272
  273        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  274        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
  275        assert_eq!(editor.text(cx), "ābcdè");
  276        assert_eq!(
  277            editor.marked_text_ranges(cx),
  278            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  279        );
  280
  281        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  282        editor.replace_text_in_range(Some(4..999), "ę", cx);
  283        assert_eq!(editor.text(cx), "ābcdę");
  284        assert_eq!(editor.marked_text_ranges(cx), None);
  285
  286        // Start a new IME composition with multiple cursors.
  287        editor.change_selections(None, cx, |s| {
  288            s.select_ranges([
  289                OffsetUtf16(1)..OffsetUtf16(1),
  290                OffsetUtf16(3)..OffsetUtf16(3),
  291                OffsetUtf16(5)..OffsetUtf16(5),
  292            ])
  293        });
  294        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
  295        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  296        assert_eq!(
  297            editor.marked_text_ranges(cx),
  298            Some(vec![
  299                OffsetUtf16(0)..OffsetUtf16(3),
  300                OffsetUtf16(4)..OffsetUtf16(7),
  301                OffsetUtf16(8)..OffsetUtf16(11)
  302            ])
  303        );
  304
  305        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  306        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
  307        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  308        assert_eq!(
  309            editor.marked_text_ranges(cx),
  310            Some(vec![
  311                OffsetUtf16(1)..OffsetUtf16(2),
  312                OffsetUtf16(5)..OffsetUtf16(6),
  313                OffsetUtf16(9)..OffsetUtf16(10)
  314            ])
  315        );
  316
  317        // Finalize IME composition with multiple cursors.
  318        editor.replace_text_in_range(Some(9..10), "2", cx);
  319        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  320        assert_eq!(editor.marked_text_ranges(cx), None);
  321
  322        editor
  323    });
  324}
  325
  326#[gpui::test]
  327fn test_selection_with_mouse(cx: &mut TestAppContext) {
  328    init_test(cx, |_| {});
  329
  330    let editor = cx.add_window(|cx| {
  331        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  332        build_editor(buffer, cx)
  333    });
  334
  335    _ = editor.update(cx, |view, cx| {
  336        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
  337    });
  338    assert_eq!(
  339        editor
  340            .update(cx, |view, cx| view.selections.display_ranges(cx))
  341            .unwrap(),
  342        [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
  343    );
  344
  345    _ = editor.update(cx, |view, cx| {
  346        view.update_selection(
  347            DisplayPoint::new(3, 3),
  348            0,
  349            gpui::Point::<f32>::default(),
  350            cx,
  351        );
  352    });
  353
  354    assert_eq!(
  355        editor
  356            .update(cx, |view, cx| view.selections.display_ranges(cx))
  357            .unwrap(),
  358        [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
  359    );
  360
  361    _ = editor.update(cx, |view, cx| {
  362        view.update_selection(
  363            DisplayPoint::new(1, 1),
  364            0,
  365            gpui::Point::<f32>::default(),
  366            cx,
  367        );
  368    });
  369
  370    assert_eq!(
  371        editor
  372            .update(cx, |view, cx| view.selections.display_ranges(cx))
  373            .unwrap(),
  374        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
  375    );
  376
  377    _ = editor.update(cx, |view, cx| {
  378        view.end_selection(cx);
  379        view.update_selection(
  380            DisplayPoint::new(3, 3),
  381            0,
  382            gpui::Point::<f32>::default(),
  383            cx,
  384        );
  385    });
  386
  387    assert_eq!(
  388        editor
  389            .update(cx, |view, cx| view.selections.display_ranges(cx))
  390            .unwrap(),
  391        [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
  392    );
  393
  394    _ = editor.update(cx, |view, cx| {
  395        view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
  396        view.update_selection(
  397            DisplayPoint::new(0, 0),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            cx,
  401        );
  402    });
  403
  404    assert_eq!(
  405        editor
  406            .update(cx, |view, cx| view.selections.display_ranges(cx))
  407            .unwrap(),
  408        [
  409            DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
  410            DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
  411        ]
  412    );
  413
  414    _ = editor.update(cx, |view, cx| {
  415        view.end_selection(cx);
  416    });
  417
  418    assert_eq!(
  419        editor
  420            .update(cx, |view, cx| view.selections.display_ranges(cx))
  421            .unwrap(),
  422        [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
  423    );
  424}
  425
  426#[gpui::test]
  427fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  428    init_test(cx, |_| {});
  429
  430    let view = cx.add_window(|cx| {
  431        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  432        build_editor(buffer, cx)
  433    });
  434
  435    _ = view.update(cx, |view, cx| {
  436        view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
  437        assert_eq!(
  438            view.selections.display_ranges(cx),
  439            [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
  440        );
  441    });
  442
  443    _ = view.update(cx, |view, cx| {
  444        view.update_selection(
  445            DisplayPoint::new(3, 3),
  446            0,
  447            gpui::Point::<f32>::default(),
  448            cx,
  449        );
  450        assert_eq!(
  451            view.selections.display_ranges(cx),
  452            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
  453        );
  454    });
  455
  456    _ = view.update(cx, |view, cx| {
  457        view.cancel(&Cancel, cx);
  458        view.update_selection(
  459            DisplayPoint::new(1, 1),
  460            0,
  461            gpui::Point::<f32>::default(),
  462            cx,
  463        );
  464        assert_eq!(
  465            view.selections.display_ranges(cx),
  466            [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
  467        );
  468    });
  469}
  470
  471#[gpui::test]
  472fn test_clone(cx: &mut TestAppContext) {
  473    init_test(cx, |_| {});
  474
  475    let (text, selection_ranges) = marked_text_ranges(
  476        indoc! {"
  477            one
  478            two
  479            threeˇ
  480            four
  481            fiveˇ
  482        "},
  483        true,
  484    );
  485
  486    let editor = cx.add_window(|cx| {
  487        let buffer = MultiBuffer::build_simple(&text, cx);
  488        build_editor(buffer, cx)
  489    });
  490
  491    _ = editor.update(cx, |editor, cx| {
  492        editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
  493        editor.fold_ranges(
  494            [
  495                Point::new(1, 0)..Point::new(2, 0),
  496                Point::new(3, 0)..Point::new(4, 0),
  497            ],
  498            true,
  499            cx,
  500        );
  501    });
  502
  503    let cloned_editor = editor
  504        .update(cx, |editor, cx| {
  505            cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
  506        })
  507        .unwrap();
  508
  509    let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
  510    let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
  511
  512    assert_eq!(
  513        cloned_editor
  514            .update(cx, |e, cx| e.display_text(cx))
  515            .unwrap(),
  516        editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
  517    );
  518    assert_eq!(
  519        cloned_snapshot
  520            .folds_in_range(0..text.len())
  521            .collect::<Vec<_>>(),
  522        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  523    );
  524    assert_set_eq!(
  525        cloned_editor
  526            .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
  527            .unwrap(),
  528        editor
  529            .update(cx, |editor, cx| editor.selections.ranges(cx))
  530            .unwrap()
  531    );
  532    assert_set_eq!(
  533        cloned_editor
  534            .update(cx, |e, cx| e.selections.display_ranges(cx))
  535            .unwrap(),
  536        editor
  537            .update(cx, |e, cx| e.selections.display_ranges(cx))
  538            .unwrap()
  539    );
  540}
  541
  542#[gpui::test]
  543async fn test_navigation_history(cx: &mut TestAppContext) {
  544    init_test(cx, |_| {});
  545
  546    use workspace::item::Item;
  547
  548    let fs = FakeFs::new(cx.executor());
  549    let project = Project::test(fs, [], cx).await;
  550    let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
  551    let pane = workspace
  552        .update(cx, |workspace, _| workspace.active_pane().clone())
  553        .unwrap();
  554
  555    _ = workspace.update(cx, |_v, cx| {
  556        cx.new_view(|cx| {
  557            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  558            let mut editor = build_editor(buffer.clone(), cx);
  559            let handle = cx.view();
  560            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  561
  562            fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
  563                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  564            }
  565
  566            // Move the cursor a small distance.
  567            // Nothing is added to the navigation history.
  568            editor.change_selections(None, cx, |s| {
  569                s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
  570            });
  571            editor.change_selections(None, cx, |s| {
  572                s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
  573            });
  574            assert!(pop_history(&mut editor, cx).is_none());
  575
  576            // Move the cursor a large distance.
  577            // The history can jump back to the previous position.
  578            editor.change_selections(None, cx, |s| {
  579                s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
  580            });
  581            let nav_entry = pop_history(&mut editor, cx).unwrap();
  582            editor.navigate(nav_entry.data.unwrap(), cx);
  583            assert_eq!(nav_entry.item.id(), cx.entity_id());
  584            assert_eq!(
  585                editor.selections.display_ranges(cx),
  586                &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
  587            );
  588            assert!(pop_history(&mut editor, cx).is_none());
  589
  590            // Move the cursor a small distance via the mouse.
  591            // Nothing is added to the navigation history.
  592            editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
  593            editor.end_selection(cx);
  594            assert_eq!(
  595                editor.selections.display_ranges(cx),
  596                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
  597            );
  598            assert!(pop_history(&mut editor, cx).is_none());
  599
  600            // Move the cursor a large distance via the mouse.
  601            // The history can jump back to the previous position.
  602            editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
  603            editor.end_selection(cx);
  604            assert_eq!(
  605                editor.selections.display_ranges(cx),
  606                &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
  607            );
  608            let nav_entry = pop_history(&mut editor, cx).unwrap();
  609            editor.navigate(nav_entry.data.unwrap(), cx);
  610            assert_eq!(nav_entry.item.id(), cx.entity_id());
  611            assert_eq!(
  612                editor.selections.display_ranges(cx),
  613                &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
  614            );
  615            assert!(pop_history(&mut editor, cx).is_none());
  616
  617            // Set scroll position to check later
  618            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
  619            let original_scroll_position = editor.scroll_manager.anchor();
  620
  621            // Jump to the end of the document and adjust scroll
  622            editor.move_to_end(&MoveToEnd, cx);
  623            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
  624            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  625
  626            let nav_entry = pop_history(&mut editor, cx).unwrap();
  627            editor.navigate(nav_entry.data.unwrap(), cx);
  628            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  629
  630            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  631            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  632            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  633            let invalid_point = Point::new(9999, 0);
  634            editor.navigate(
  635                Box::new(NavigationData {
  636                    cursor_anchor: invalid_anchor,
  637                    cursor_position: invalid_point,
  638                    scroll_anchor: ScrollAnchor {
  639                        anchor: invalid_anchor,
  640                        offset: Default::default(),
  641                    },
  642                    scroll_top_row: invalid_point.row,
  643                }),
  644                cx,
  645            );
  646            assert_eq!(
  647                editor.selections.display_ranges(cx),
  648                &[editor.max_point(cx)..editor.max_point(cx)]
  649            );
  650            assert_eq!(
  651                editor.scroll_position(cx),
  652                gpui::Point::new(0., editor.max_point(cx).row() as f32)
  653            );
  654
  655            editor
  656        })
  657    });
  658}
  659
  660#[gpui::test]
  661fn test_cancel(cx: &mut TestAppContext) {
  662    init_test(cx, |_| {});
  663
  664    let view = cx.add_window(|cx| {
  665        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  666        build_editor(buffer, cx)
  667    });
  668
  669    _ = view.update(cx, |view, cx| {
  670        view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
  671        view.update_selection(
  672            DisplayPoint::new(1, 1),
  673            0,
  674            gpui::Point::<f32>::default(),
  675            cx,
  676        );
  677        view.end_selection(cx);
  678
  679        view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
  680        view.update_selection(
  681            DisplayPoint::new(0, 3),
  682            0,
  683            gpui::Point::<f32>::default(),
  684            cx,
  685        );
  686        view.end_selection(cx);
  687        assert_eq!(
  688            view.selections.display_ranges(cx),
  689            [
  690                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
  691                DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
  692            ]
  693        );
  694    });
  695
  696    _ = view.update(cx, |view, cx| {
  697        view.cancel(&Cancel, cx);
  698        assert_eq!(
  699            view.selections.display_ranges(cx),
  700            [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
  701        );
  702    });
  703
  704    _ = view.update(cx, |view, cx| {
  705        view.cancel(&Cancel, cx);
  706        assert_eq!(
  707            view.selections.display_ranges(cx),
  708            [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
  709        );
  710    });
  711}
  712
  713#[gpui::test]
  714fn test_fold_action(cx: &mut TestAppContext) {
  715    init_test(cx, |_| {});
  716
  717    let view = cx.add_window(|cx| {
  718        let buffer = MultiBuffer::build_simple(
  719            &"
  720                impl Foo {
  721                    // Hello!
  722
  723                    fn a() {
  724                        1
  725                    }
  726
  727                    fn b() {
  728                        2
  729                    }
  730
  731                    fn c() {
  732                        3
  733                    }
  734                }
  735            "
  736            .unindent(),
  737            cx,
  738        );
  739        build_editor(buffer.clone(), cx)
  740    });
  741
  742    _ = view.update(cx, |view, cx| {
  743        view.change_selections(None, cx, |s| {
  744            s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
  745        });
  746        view.fold(&Fold, cx);
  747        assert_eq!(
  748            view.display_text(cx),
  749            "
  750                impl Foo {
  751                    // Hello!
  752
  753                    fn a() {
  754                        1
  755                    }
  756
  757                    fn b() {⋯
  758                    }
  759
  760                    fn c() {⋯
  761                    }
  762                }
  763            "
  764            .unindent(),
  765        );
  766
  767        view.fold(&Fold, cx);
  768        assert_eq!(
  769            view.display_text(cx),
  770            "
  771                impl Foo {⋯
  772                }
  773            "
  774            .unindent(),
  775        );
  776
  777        view.unfold_lines(&UnfoldLines, cx);
  778        assert_eq!(
  779            view.display_text(cx),
  780            "
  781                impl Foo {
  782                    // Hello!
  783
  784                    fn a() {
  785                        1
  786                    }
  787
  788                    fn b() {⋯
  789                    }
  790
  791                    fn c() {⋯
  792                    }
  793                }
  794            "
  795            .unindent(),
  796        );
  797
  798        view.unfold_lines(&UnfoldLines, cx);
  799        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
  800    });
  801}
  802
  803#[gpui::test]
  804fn test_move_cursor(cx: &mut TestAppContext) {
  805    init_test(cx, |_| {});
  806
  807    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
  808    let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
  809
  810    _ = buffer.update(cx, |buffer, cx| {
  811        buffer.edit(
  812            vec![
  813                (Point::new(1, 0)..Point::new(1, 0), "\t"),
  814                (Point::new(1, 1)..Point::new(1, 1), "\t"),
  815            ],
  816            None,
  817            cx,
  818        );
  819    });
  820    _ = view.update(cx, |view, cx| {
  821        assert_eq!(
  822            view.selections.display_ranges(cx),
  823            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
  824        );
  825
  826        view.move_down(&MoveDown, cx);
  827        assert_eq!(
  828            view.selections.display_ranges(cx),
  829            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
  830        );
  831
  832        view.move_right(&MoveRight, cx);
  833        assert_eq!(
  834            view.selections.display_ranges(cx),
  835            &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
  836        );
  837
  838        view.move_left(&MoveLeft, cx);
  839        assert_eq!(
  840            view.selections.display_ranges(cx),
  841            &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
  842        );
  843
  844        view.move_up(&MoveUp, cx);
  845        assert_eq!(
  846            view.selections.display_ranges(cx),
  847            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
  848        );
  849
  850        view.move_to_end(&MoveToEnd, cx);
  851        assert_eq!(
  852            view.selections.display_ranges(cx),
  853            &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
  854        );
  855
  856        view.move_to_beginning(&MoveToBeginning, cx);
  857        assert_eq!(
  858            view.selections.display_ranges(cx),
  859            &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
  860        );
  861
  862        view.change_selections(None, cx, |s| {
  863            s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
  864        });
  865        view.select_to_beginning(&SelectToBeginning, cx);
  866        assert_eq!(
  867            view.selections.display_ranges(cx),
  868            &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
  869        );
  870
  871        view.select_to_end(&SelectToEnd, cx);
  872        assert_eq!(
  873            view.selections.display_ranges(cx),
  874            &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
  875        );
  876    });
  877}
  878
  879#[gpui::test]
  880fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
  881    init_test(cx, |_| {});
  882
  883    let view = cx.add_window(|cx| {
  884        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
  885        build_editor(buffer.clone(), cx)
  886    });
  887
  888    assert_eq!('ⓐ'.len_utf8(), 3);
  889    assert_eq!('α'.len_utf8(), 2);
  890
  891    _ = view.update(cx, |view, cx| {
  892        view.fold_ranges(
  893            vec![
  894                Point::new(0, 6)..Point::new(0, 12),
  895                Point::new(1, 2)..Point::new(1, 4),
  896                Point::new(2, 4)..Point::new(2, 8),
  897            ],
  898            true,
  899            cx,
  900        );
  901        assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
  902
  903        view.move_right(&MoveRight, cx);
  904        assert_eq!(
  905            view.selections.display_ranges(cx),
  906            &[empty_range(0, "".len())]
  907        );
  908        view.move_right(&MoveRight, cx);
  909        assert_eq!(
  910            view.selections.display_ranges(cx),
  911            &[empty_range(0, "ⓐⓑ".len())]
  912        );
  913        view.move_right(&MoveRight, cx);
  914        assert_eq!(
  915            view.selections.display_ranges(cx),
  916            &[empty_range(0, "ⓐⓑ⋯".len())]
  917        );
  918
  919        view.move_down(&MoveDown, cx);
  920        assert_eq!(
  921            view.selections.display_ranges(cx),
  922            &[empty_range(1, "ab⋯e".len())]
  923        );
  924        view.move_left(&MoveLeft, cx);
  925        assert_eq!(
  926            view.selections.display_ranges(cx),
  927            &[empty_range(1, "ab⋯".len())]
  928        );
  929        view.move_left(&MoveLeft, cx);
  930        assert_eq!(
  931            view.selections.display_ranges(cx),
  932            &[empty_range(1, "ab".len())]
  933        );
  934        view.move_left(&MoveLeft, cx);
  935        assert_eq!(
  936            view.selections.display_ranges(cx),
  937            &[empty_range(1, "a".len())]
  938        );
  939
  940        view.move_down(&MoveDown, cx);
  941        assert_eq!(
  942            view.selections.display_ranges(cx),
  943            &[empty_range(2, "α".len())]
  944        );
  945        view.move_right(&MoveRight, cx);
  946        assert_eq!(
  947            view.selections.display_ranges(cx),
  948            &[empty_range(2, "αβ".len())]
  949        );
  950        view.move_right(&MoveRight, cx);
  951        assert_eq!(
  952            view.selections.display_ranges(cx),
  953            &[empty_range(2, "αβ⋯".len())]
  954        );
  955        view.move_right(&MoveRight, cx);
  956        assert_eq!(
  957            view.selections.display_ranges(cx),
  958            &[empty_range(2, "αβ⋯ε".len())]
  959        );
  960
  961        view.move_up(&MoveUp, cx);
  962        assert_eq!(
  963            view.selections.display_ranges(cx),
  964            &[empty_range(1, "ab⋯e".len())]
  965        );
  966        view.move_down(&MoveDown, cx);
  967        assert_eq!(
  968            view.selections.display_ranges(cx),
  969            &[empty_range(2, "αβ⋯ε".len())]
  970        );
  971        view.move_up(&MoveUp, cx);
  972        assert_eq!(
  973            view.selections.display_ranges(cx),
  974            &[empty_range(1, "ab⋯e".len())]
  975        );
  976
  977        view.move_up(&MoveUp, cx);
  978        assert_eq!(
  979            view.selections.display_ranges(cx),
  980            &[empty_range(0, "ⓐⓑ".len())]
  981        );
  982        view.move_left(&MoveLeft, cx);
  983        assert_eq!(
  984            view.selections.display_ranges(cx),
  985            &[empty_range(0, "".len())]
  986        );
  987        view.move_left(&MoveLeft, cx);
  988        assert_eq!(
  989            view.selections.display_ranges(cx),
  990            &[empty_range(0, "".len())]
  991        );
  992    });
  993}
  994
  995#[gpui::test]
  996fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
  997    init_test(cx, |_| {});
  998
  999    let view = cx.add_window(|cx| {
 1000        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1001        build_editor(buffer.clone(), cx)
 1002    });
 1003    _ = view.update(cx, |view, cx| {
 1004        view.change_selections(None, cx, |s| {
 1005            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1006        });
 1007        view.move_down(&MoveDown, cx);
 1008        assert_eq!(
 1009            view.selections.display_ranges(cx),
 1010            &[empty_range(1, "abcd".len())]
 1011        );
 1012
 1013        view.move_down(&MoveDown, cx);
 1014        assert_eq!(
 1015            view.selections.display_ranges(cx),
 1016            &[empty_range(2, "αβγ".len())]
 1017        );
 1018
 1019        view.move_down(&MoveDown, cx);
 1020        assert_eq!(
 1021            view.selections.display_ranges(cx),
 1022            &[empty_range(3, "abcd".len())]
 1023        );
 1024
 1025        view.move_down(&MoveDown, cx);
 1026        assert_eq!(
 1027            view.selections.display_ranges(cx),
 1028            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1029        );
 1030
 1031        view.move_up(&MoveUp, cx);
 1032        assert_eq!(
 1033            view.selections.display_ranges(cx),
 1034            &[empty_range(3, "abcd".len())]
 1035        );
 1036
 1037        view.move_up(&MoveUp, cx);
 1038        assert_eq!(
 1039            view.selections.display_ranges(cx),
 1040            &[empty_range(2, "αβγ".len())]
 1041        );
 1042    });
 1043}
 1044
 1045#[gpui::test]
 1046fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1047    init_test(cx, |_| {});
 1048    let move_to_beg = MoveToBeginningOfLine {
 1049        stop_at_soft_wraps: true,
 1050    };
 1051
 1052    let move_to_end = MoveToEndOfLine {
 1053        stop_at_soft_wraps: true,
 1054    };
 1055
 1056    let view = cx.add_window(|cx| {
 1057        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1058        build_editor(buffer, cx)
 1059    });
 1060    _ = view.update(cx, |view, cx| {
 1061        view.change_selections(None, cx, |s| {
 1062            s.select_display_ranges([
 1063                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 1064                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
 1065            ]);
 1066        });
 1067    });
 1068
 1069    _ = view.update(cx, |view, cx| {
 1070        view.move_to_beginning_of_line(&move_to_beg, cx);
 1071        assert_eq!(
 1072            view.selections.display_ranges(cx),
 1073            &[
 1074                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
 1075                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
 1076            ]
 1077        );
 1078    });
 1079
 1080    _ = view.update(cx, |view, cx| {
 1081        view.move_to_beginning_of_line(&move_to_beg, cx);
 1082        assert_eq!(
 1083            view.selections.display_ranges(cx),
 1084            &[
 1085                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
 1086                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 1087            ]
 1088        );
 1089    });
 1090
 1091    _ = view.update(cx, |view, cx| {
 1092        view.move_to_beginning_of_line(&move_to_beg, cx);
 1093        assert_eq!(
 1094            view.selections.display_ranges(cx),
 1095            &[
 1096                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
 1097                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
 1098            ]
 1099        );
 1100    });
 1101
 1102    _ = view.update(cx, |view, cx| {
 1103        view.move_to_end_of_line(&move_to_end, cx);
 1104        assert_eq!(
 1105            view.selections.display_ranges(cx),
 1106            &[
 1107                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
 1108                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
 1109            ]
 1110        );
 1111    });
 1112
 1113    // Moving to the end of line again is a no-op.
 1114    _ = view.update(cx, |view, cx| {
 1115        view.move_to_end_of_line(&move_to_end, cx);
 1116        assert_eq!(
 1117            view.selections.display_ranges(cx),
 1118            &[
 1119                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
 1120                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
 1121            ]
 1122        );
 1123    });
 1124
 1125    _ = view.update(cx, |view, cx| {
 1126        view.move_left(&MoveLeft, cx);
 1127        view.select_to_beginning_of_line(
 1128            &SelectToBeginningOfLine {
 1129                stop_at_soft_wraps: true,
 1130            },
 1131            cx,
 1132        );
 1133        assert_eq!(
 1134            view.selections.display_ranges(cx),
 1135            &[
 1136                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
 1137                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
 1138            ]
 1139        );
 1140    });
 1141
 1142    _ = view.update(cx, |view, cx| {
 1143        view.select_to_beginning_of_line(
 1144            &SelectToBeginningOfLine {
 1145                stop_at_soft_wraps: true,
 1146            },
 1147            cx,
 1148        );
 1149        assert_eq!(
 1150            view.selections.display_ranges(cx),
 1151            &[
 1152                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
 1153                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
 1154            ]
 1155        );
 1156    });
 1157
 1158    _ = view.update(cx, |view, cx| {
 1159        view.select_to_beginning_of_line(
 1160            &SelectToBeginningOfLine {
 1161                stop_at_soft_wraps: true,
 1162            },
 1163            cx,
 1164        );
 1165        assert_eq!(
 1166            view.selections.display_ranges(cx),
 1167            &[
 1168                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
 1169                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
 1170            ]
 1171        );
 1172    });
 1173
 1174    _ = view.update(cx, |view, cx| {
 1175        view.select_to_end_of_line(
 1176            &SelectToEndOfLine {
 1177                stop_at_soft_wraps: true,
 1178            },
 1179            cx,
 1180        );
 1181        assert_eq!(
 1182            view.selections.display_ranges(cx),
 1183            &[
 1184                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
 1185                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
 1186            ]
 1187        );
 1188    });
 1189
 1190    _ = view.update(cx, |view, cx| {
 1191        view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
 1192        assert_eq!(view.display_text(cx), "ab\n  de");
 1193        assert_eq!(
 1194            view.selections.display_ranges(cx),
 1195            &[
 1196                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 1197                DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
 1198            ]
 1199        );
 1200    });
 1201
 1202    _ = view.update(cx, |view, cx| {
 1203        view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
 1204        assert_eq!(view.display_text(cx), "\n");
 1205        assert_eq!(
 1206            view.selections.display_ranges(cx),
 1207            &[
 1208                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
 1209                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 1210            ]
 1211        );
 1212    });
 1213}
 1214
 1215#[gpui::test]
 1216fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1217    init_test(cx, |_| {});
 1218    let move_to_beg = MoveToBeginningOfLine {
 1219        stop_at_soft_wraps: false,
 1220    };
 1221
 1222    let move_to_end = MoveToEndOfLine {
 1223        stop_at_soft_wraps: false,
 1224    };
 1225
 1226    let view = cx.add_window(|cx| {
 1227        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1228        build_editor(buffer, cx)
 1229    });
 1230
 1231    _ = view.update(cx, |view, cx| {
 1232        view.set_wrap_width(Some(140.0.into()), cx);
 1233
 1234        // We expect the following lines after wrapping
 1235        // ```
 1236        // thequickbrownfox
 1237        // jumpedoverthelazydo
 1238        // gs
 1239        // ```
 1240        // The final `gs` was soft-wrapped onto a new line.
 1241        assert_eq!(
 1242            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1243            view.display_text(cx),
 1244        );
 1245
 1246        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1247        // Start the cursor at the `k` on the first line
 1248        view.change_selections(None, cx, |s| {
 1249            s.select_display_ranges([DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7)]);
 1250        });
 1251
 1252        // Moving to the beginning of the line should put us at the beginning of the line.
 1253        view.move_to_beginning_of_line(&move_to_beg, cx);
 1254        assert_eq!(
 1255            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),],
 1256            view.selections.display_ranges(cx)
 1257        );
 1258
 1259        // Moving to the end of the line should put us at the end of the line.
 1260        view.move_to_end_of_line(&move_to_end, cx);
 1261        assert_eq!(
 1262            vec![DisplayPoint::new(0, 16)..DisplayPoint::new(0, 16),],
 1263            view.selections.display_ranges(cx)
 1264        );
 1265
 1266        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1267        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1268        view.change_selections(None, cx, |s| {
 1269            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0)]);
 1270        });
 1271
 1272        // Moving to the beginning of the line should put us at the start of the second line of
 1273        // display text, i.e., the `j`.
 1274        view.move_to_beginning_of_line(&move_to_beg, cx);
 1275        assert_eq!(
 1276            vec![DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),],
 1277            view.selections.display_ranges(cx)
 1278        );
 1279
 1280        // Moving to the beginning of the line again should be a no-op.
 1281        view.move_to_beginning_of_line(&move_to_beg, cx);
 1282        assert_eq!(
 1283            vec![DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),],
 1284            view.selections.display_ranges(cx)
 1285        );
 1286
 1287        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1288        // next display line.
 1289        view.move_to_end_of_line(&move_to_end, cx);
 1290        assert_eq!(
 1291            vec![DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),],
 1292            view.selections.display_ranges(cx)
 1293        );
 1294
 1295        // Moving to the end of the line again should be a no-op.
 1296        view.move_to_end_of_line(&move_to_end, cx);
 1297        assert_eq!(
 1298            vec![DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),],
 1299            view.selections.display_ranges(cx)
 1300        );
 1301    });
 1302}
 1303
 1304#[gpui::test]
 1305fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1306    init_test(cx, |_| {});
 1307
 1308    let view = cx.add_window(|cx| {
 1309        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1310        build_editor(buffer, cx)
 1311    });
 1312    _ = view.update(cx, |view, cx| {
 1313        view.change_selections(None, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
 1316                DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
 1317            ])
 1318        });
 1319
 1320        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1321        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
 1322
 1323        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1324        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", view, cx);
 1325
 1326        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1327        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", view, cx);
 1328
 1329        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1330        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
 1331
 1332        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1333        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", view, cx);
 1334
 1335        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1336        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", view, cx);
 1337
 1338        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1339        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", view, cx);
 1340
 1341        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1342        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", view, cx);
 1343
 1344        view.move_right(&MoveRight, cx);
 1345        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
 1346        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
 1347
 1348        view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
 1349        assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}", view, cx);
 1350
 1351        view.select_to_next_word_end(&SelectToNextWordEnd, cx);
 1352        assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}", view, cx);
 1353    });
 1354}
 1355
 1356#[gpui::test]
 1357fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1358    init_test(cx, |_| {});
 1359
 1360    let view = cx.add_window(|cx| {
 1361        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1362        build_editor(buffer, cx)
 1363    });
 1364
 1365    _ = view.update(cx, |view, cx| {
 1366        view.set_wrap_width(Some(140.0.into()), cx);
 1367        assert_eq!(
 1368            view.display_text(cx),
 1369            "use one::{\n    two::three::\n    four::five\n};"
 1370        );
 1371
 1372        view.change_selections(None, cx, |s| {
 1373            s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
 1374        });
 1375
 1376        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1377        assert_eq!(
 1378            view.selections.display_ranges(cx),
 1379            &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
 1380        );
 1381
 1382        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1383        assert_eq!(
 1384            view.selections.display_ranges(cx),
 1385            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
 1386        );
 1387
 1388        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1389        assert_eq!(
 1390            view.selections.display_ranges(cx),
 1391            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
 1392        );
 1393
 1394        view.move_to_next_word_end(&MoveToNextWordEnd, cx);
 1395        assert_eq!(
 1396            view.selections.display_ranges(cx),
 1397            &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
 1398        );
 1399
 1400        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1401        assert_eq!(
 1402            view.selections.display_ranges(cx),
 1403            &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
 1404        );
 1405
 1406        view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
 1407        assert_eq!(
 1408            view.selections.display_ranges(cx),
 1409            &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
 1410        );
 1411    });
 1412}
 1413
 1414#[gpui::test]
 1415async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
 1416    init_test(cx, |_| {});
 1417    let mut cx = EditorTestContext::new(cx).await;
 1418
 1419    let line_height = cx.editor(|editor, cx| {
 1420        editor
 1421            .style()
 1422            .unwrap()
 1423            .text
 1424            .line_height_in_pixels(cx.rem_size())
 1425    });
 1426    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 1427
 1428    cx.set_state(
 1429        &r#"ˇone
 1430        two
 1431
 1432        three
 1433        fourˇ
 1434        five
 1435
 1436        six"#
 1437            .unindent(),
 1438    );
 1439
 1440    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
 1441    cx.assert_editor_state(
 1442        &r#"one
 1443        two
 1444        ˇ
 1445        three
 1446        four
 1447        five
 1448        ˇ
 1449        six"#
 1450            .unindent(),
 1451    );
 1452
 1453    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
 1454    cx.assert_editor_state(
 1455        &r#"one
 1456        two
 1457
 1458        three
 1459        four
 1460        five
 1461        ˇ
 1462        sixˇ"#
 1463            .unindent(),
 1464    );
 1465
 1466    cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
 1467    cx.assert_editor_state(
 1468        &r#"one
 1469        two
 1470
 1471        three
 1472        four
 1473        five
 1474
 1475        sixˇ"#
 1476            .unindent(),
 1477    );
 1478
 1479    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
 1480    cx.assert_editor_state(
 1481        &r#"one
 1482        two
 1483
 1484        three
 1485        four
 1486        five
 1487        ˇ
 1488        six"#
 1489            .unindent(),
 1490    );
 1491
 1492    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
 1493    cx.assert_editor_state(
 1494        &r#"one
 1495        two
 1496        ˇ
 1497        three
 1498        four
 1499        five
 1500
 1501        six"#
 1502            .unindent(),
 1503    );
 1504
 1505    cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
 1506    cx.assert_editor_state(
 1507        &r#"ˇone
 1508        two
 1509
 1510        three
 1511        four
 1512        five
 1513
 1514        six"#
 1515            .unindent(),
 1516    );
 1517}
 1518
 1519#[gpui::test]
 1520async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
 1521    init_test(cx, |_| {});
 1522    let mut cx = EditorTestContext::new(cx).await;
 1523    let line_height = cx.editor(|editor, cx| {
 1524        editor
 1525            .style()
 1526            .unwrap()
 1527            .text
 1528            .line_height_in_pixels(cx.rem_size())
 1529    });
 1530    let window = cx.window;
 1531    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 1532
 1533    cx.set_state(
 1534        &r#"ˇone
 1535        two
 1536        three
 1537        four
 1538        five
 1539        six
 1540        seven
 1541        eight
 1542        nine
 1543        ten
 1544        "#,
 1545    );
 1546
 1547    cx.update_editor(|editor, cx| {
 1548        assert_eq!(
 1549            editor.snapshot(cx).scroll_position(),
 1550            gpui::Point::new(0., 0.)
 1551        );
 1552        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
 1553        assert_eq!(
 1554            editor.snapshot(cx).scroll_position(),
 1555            gpui::Point::new(0., 3.)
 1556        );
 1557        editor.scroll_screen(&ScrollAmount::Page(1.), cx);
 1558        assert_eq!(
 1559            editor.snapshot(cx).scroll_position(),
 1560            gpui::Point::new(0., 6.)
 1561        );
 1562        editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
 1563        assert_eq!(
 1564            editor.snapshot(cx).scroll_position(),
 1565            gpui::Point::new(0., 3.)
 1566        );
 1567
 1568        editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
 1569        assert_eq!(
 1570            editor.snapshot(cx).scroll_position(),
 1571            gpui::Point::new(0., 1.)
 1572        );
 1573        editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
 1574        assert_eq!(
 1575            editor.snapshot(cx).scroll_position(),
 1576            gpui::Point::new(0., 3.)
 1577        );
 1578    });
 1579}
 1580
 1581#[gpui::test]
 1582async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
 1583    init_test(cx, |_| {});
 1584    let mut cx = EditorTestContext::new(cx).await;
 1585
 1586    let line_height = cx.update_editor(|editor, cx| {
 1587        editor.set_vertical_scroll_margin(2, cx);
 1588        editor
 1589            .style()
 1590            .unwrap()
 1591            .text
 1592            .line_height_in_pixels(cx.rem_size())
 1593    });
 1594    let window = cx.window;
 1595    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 1596
 1597    cx.set_state(
 1598        &r#"ˇone
 1599            two
 1600            three
 1601            four
 1602            five
 1603            six
 1604            seven
 1605            eight
 1606            nine
 1607            ten
 1608        "#,
 1609    );
 1610    cx.update_editor(|editor, cx| {
 1611        assert_eq!(
 1612            editor.snapshot(cx).scroll_position(),
 1613            gpui::Point::new(0., 0.0)
 1614        );
 1615    });
 1616
 1617    // Add a cursor below the visible area. Since both cursors cannot fit
 1618    // on screen, the editor autoscrolls to reveal the newest cursor, and
 1619    // allows the vertical scroll margin below that cursor.
 1620    cx.update_editor(|editor, cx| {
 1621        editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
 1622            selections.select_ranges([
 1623                Point::new(0, 0)..Point::new(0, 0),
 1624                Point::new(6, 0)..Point::new(6, 0),
 1625            ]);
 1626        })
 1627    });
 1628    cx.update_editor(|editor, cx| {
 1629        assert_eq!(
 1630            editor.snapshot(cx).scroll_position(),
 1631            gpui::Point::new(0., 3.0)
 1632        );
 1633    });
 1634
 1635    // Move down. The editor cursor scrolls down to track the newest cursor.
 1636    cx.update_editor(|editor, cx| {
 1637        editor.move_down(&Default::default(), cx);
 1638    });
 1639    cx.update_editor(|editor, cx| {
 1640        assert_eq!(
 1641            editor.snapshot(cx).scroll_position(),
 1642            gpui::Point::new(0., 4.0)
 1643        );
 1644    });
 1645
 1646    // Add a cursor above the visible area. Since both cursors fit on screen,
 1647    // the editor scrolls to show both.
 1648    cx.update_editor(|editor, cx| {
 1649        editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
 1650            selections.select_ranges([
 1651                Point::new(1, 0)..Point::new(1, 0),
 1652                Point::new(6, 0)..Point::new(6, 0),
 1653            ]);
 1654        })
 1655    });
 1656    cx.update_editor(|editor, cx| {
 1657        assert_eq!(
 1658            editor.snapshot(cx).scroll_position(),
 1659            gpui::Point::new(0., 1.0)
 1660        );
 1661    });
 1662}
 1663
 1664#[gpui::test]
 1665async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
 1666    init_test(cx, |_| {});
 1667    let mut cx = EditorTestContext::new(cx).await;
 1668
 1669    let line_height = cx.editor(|editor, cx| {
 1670        editor
 1671            .style()
 1672            .unwrap()
 1673            .text
 1674            .line_height_in_pixels(cx.rem_size())
 1675    });
 1676    let window = cx.window;
 1677    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 1678    cx.set_state(
 1679        &r#"
 1680        ˇone
 1681        two
 1682        threeˇ
 1683        four
 1684        five
 1685        six
 1686        seven
 1687        eight
 1688        nine
 1689        ten
 1690        "#
 1691        .unindent(),
 1692    );
 1693
 1694    cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
 1695    cx.assert_editor_state(
 1696        &r#"
 1697        one
 1698        two
 1699        three
 1700        ˇfour
 1701        five
 1702        sixˇ
 1703        seven
 1704        eight
 1705        nine
 1706        ten
 1707        "#
 1708        .unindent(),
 1709    );
 1710
 1711    cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
 1712    cx.assert_editor_state(
 1713        &r#"
 1714        one
 1715        two
 1716        three
 1717        four
 1718        five
 1719        six
 1720        ˇseven
 1721        eight
 1722        nineˇ
 1723        ten
 1724        "#
 1725        .unindent(),
 1726    );
 1727
 1728    cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
 1729    cx.assert_editor_state(
 1730        &r#"
 1731        one
 1732        two
 1733        three
 1734        ˇfour
 1735        five
 1736        sixˇ
 1737        seven
 1738        eight
 1739        nine
 1740        ten
 1741        "#
 1742        .unindent(),
 1743    );
 1744
 1745    cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
 1746    cx.assert_editor_state(
 1747        &r#"
 1748        ˇone
 1749        two
 1750        threeˇ
 1751        four
 1752        five
 1753        six
 1754        seven
 1755        eight
 1756        nine
 1757        ten
 1758        "#
 1759        .unindent(),
 1760    );
 1761
 1762    // Test select collapsing
 1763    cx.update_editor(|editor, cx| {
 1764        editor.move_page_down(&MovePageDown::default(), cx);
 1765        editor.move_page_down(&MovePageDown::default(), cx);
 1766        editor.move_page_down(&MovePageDown::default(), cx);
 1767    });
 1768    cx.assert_editor_state(
 1769        &r#"
 1770        one
 1771        two
 1772        three
 1773        four
 1774        five
 1775        six
 1776        seven
 1777        eight
 1778        nine
 1779        ˇten
 1780        ˇ"#
 1781        .unindent(),
 1782    );
 1783}
 1784
 1785#[gpui::test]
 1786async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
 1787    init_test(cx, |_| {});
 1788    let mut cx = EditorTestContext::new(cx).await;
 1789    cx.set_state("one «two threeˇ» four");
 1790    cx.update_editor(|editor, cx| {
 1791        editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
 1792        assert_eq!(editor.text(cx), " four");
 1793    });
 1794}
 1795
 1796#[gpui::test]
 1797fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 1798    init_test(cx, |_| {});
 1799
 1800    let view = cx.add_window(|cx| {
 1801        let buffer = MultiBuffer::build_simple("one two three four", cx);
 1802        build_editor(buffer.clone(), cx)
 1803    });
 1804
 1805    _ = view.update(cx, |view, cx| {
 1806        view.change_selections(None, cx, |s| {
 1807            s.select_display_ranges([
 1808                // an empty selection - the preceding word fragment is deleted
 1809                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 1810                // characters selected - they are deleted
 1811                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
 1812            ])
 1813        });
 1814        view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
 1815        assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
 1816    });
 1817
 1818    _ = view.update(cx, |view, cx| {
 1819        view.change_selections(None, cx, |s| {
 1820            s.select_display_ranges([
 1821                // an empty selection - the following word fragment is deleted
 1822                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
 1823                // characters selected - they are deleted
 1824                DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
 1825            ])
 1826        });
 1827        view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
 1828        assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
 1829    });
 1830}
 1831
 1832#[gpui::test]
 1833fn test_newline(cx: &mut TestAppContext) {
 1834    init_test(cx, |_| {});
 1835
 1836    let view = cx.add_window(|cx| {
 1837        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 1838        build_editor(buffer.clone(), cx)
 1839    });
 1840
 1841    _ = view.update(cx, |view, cx| {
 1842        view.change_selections(None, cx, |s| {
 1843            s.select_display_ranges([
 1844                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 1845                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
 1846                DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
 1847            ])
 1848        });
 1849
 1850        view.newline(&Newline, cx);
 1851        assert_eq!(view.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 1852    });
 1853}
 1854
 1855#[gpui::test]
 1856fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 1857    init_test(cx, |_| {});
 1858
 1859    let editor = cx.add_window(|cx| {
 1860        let buffer = MultiBuffer::build_simple(
 1861            "
 1862                a
 1863                b(
 1864                    X
 1865                )
 1866                c(
 1867                    X
 1868                )
 1869            "
 1870            .unindent()
 1871            .as_str(),
 1872            cx,
 1873        );
 1874        let mut editor = build_editor(buffer.clone(), cx);
 1875        editor.change_selections(None, cx, |s| {
 1876            s.select_ranges([
 1877                Point::new(2, 4)..Point::new(2, 5),
 1878                Point::new(5, 4)..Point::new(5, 5),
 1879            ])
 1880        });
 1881        editor
 1882    });
 1883
 1884    _ = editor.update(cx, |editor, cx| {
 1885        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 1886        editor.buffer.update(cx, |buffer, cx| {
 1887            buffer.edit(
 1888                [
 1889                    (Point::new(1, 2)..Point::new(3, 0), ""),
 1890                    (Point::new(4, 2)..Point::new(6, 0), ""),
 1891                ],
 1892                None,
 1893                cx,
 1894            );
 1895            assert_eq!(
 1896                buffer.read(cx).text(),
 1897                "
 1898                    a
 1899                    b()
 1900                    c()
 1901                "
 1902                .unindent()
 1903            );
 1904        });
 1905        assert_eq!(
 1906            editor.selections.ranges(cx),
 1907            &[
 1908                Point::new(1, 2)..Point::new(1, 2),
 1909                Point::new(2, 2)..Point::new(2, 2),
 1910            ],
 1911        );
 1912
 1913        editor.newline(&Newline, cx);
 1914        assert_eq!(
 1915            editor.text(cx),
 1916            "
 1917                a
 1918                b(
 1919                )
 1920                c(
 1921                )
 1922            "
 1923            .unindent()
 1924        );
 1925
 1926        // The selections are moved after the inserted newlines
 1927        assert_eq!(
 1928            editor.selections.ranges(cx),
 1929            &[
 1930                Point::new(2, 0)..Point::new(2, 0),
 1931                Point::new(4, 0)..Point::new(4, 0),
 1932            ],
 1933        );
 1934    });
 1935}
 1936
 1937#[gpui::test]
 1938async fn test_newline_above(cx: &mut gpui::TestAppContext) {
 1939    init_test(cx, |settings| {
 1940        settings.defaults.tab_size = NonZeroU32::new(4)
 1941    });
 1942
 1943    let language = Arc::new(
 1944        Language::new(
 1945            LanguageConfig::default(),
 1946            Some(tree_sitter_rust::language()),
 1947        )
 1948        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 1949        .unwrap(),
 1950    );
 1951
 1952    let mut cx = EditorTestContext::new(cx).await;
 1953    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 1954    cx.set_state(indoc! {"
 1955        const a: ˇA = (
 1956 1957                «const_functionˇ»(ˇ),
 1958                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 1959 1960        ˇ);ˇ
 1961    "});
 1962
 1963    cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
 1964    cx.assert_editor_state(indoc! {"
 1965        ˇ
 1966        const a: A = (
 1967            ˇ
 1968            (
 1969                ˇ
 1970                ˇ
 1971                const_function(),
 1972                ˇ
 1973                ˇ
 1974                ˇ
 1975                ˇ
 1976                something_else,
 1977                ˇ
 1978            )
 1979            ˇ
 1980            ˇ
 1981        );
 1982    "});
 1983}
 1984
 1985#[gpui::test]
 1986async fn test_newline_below(cx: &mut gpui::TestAppContext) {
 1987    init_test(cx, |settings| {
 1988        settings.defaults.tab_size = NonZeroU32::new(4)
 1989    });
 1990
 1991    let language = Arc::new(
 1992        Language::new(
 1993            LanguageConfig::default(),
 1994            Some(tree_sitter_rust::language()),
 1995        )
 1996        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 1997        .unwrap(),
 1998    );
 1999
 2000    let mut cx = EditorTestContext::new(cx).await;
 2001    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2002    cx.set_state(indoc! {"
 2003        const a: ˇA = (
 2004 2005                «const_functionˇ»(ˇ),
 2006                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2007 2008        ˇ);ˇ
 2009    "});
 2010
 2011    cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
 2012    cx.assert_editor_state(indoc! {"
 2013        const a: A = (
 2014            ˇ
 2015            (
 2016                ˇ
 2017                const_function(),
 2018                ˇ
 2019                ˇ
 2020                something_else,
 2021                ˇ
 2022                ˇ
 2023                ˇ
 2024                ˇ
 2025            )
 2026            ˇ
 2027        );
 2028        ˇ
 2029        ˇ
 2030    "});
 2031}
 2032
 2033#[gpui::test]
 2034async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
 2035    init_test(cx, |settings| {
 2036        settings.defaults.tab_size = NonZeroU32::new(4)
 2037    });
 2038
 2039    let language = Arc::new(Language::new(
 2040        LanguageConfig {
 2041            line_comments: vec!["//".into()],
 2042            ..LanguageConfig::default()
 2043        },
 2044        None,
 2045    ));
 2046    {
 2047        let mut cx = EditorTestContext::new(cx).await;
 2048        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2049        cx.set_state(indoc! {"
 2050        // Fooˇ
 2051    "});
 2052
 2053        cx.update_editor(|e, cx| e.newline(&Newline, cx));
 2054        cx.assert_editor_state(indoc! {"
 2055        // Foo
 2056        //ˇ
 2057    "});
 2058        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2059        cx.set_state(indoc! {"
 2060        ˇ// Foo
 2061    "});
 2062        cx.update_editor(|e, cx| e.newline(&Newline, cx));
 2063        cx.assert_editor_state(indoc! {"
 2064
 2065        ˇ// Foo
 2066    "});
 2067    }
 2068    // Ensure that comment continuations can be disabled.
 2069    update_test_language_settings(cx, |settings| {
 2070        settings.defaults.extend_comment_on_newline = Some(false);
 2071    });
 2072    let mut cx = EditorTestContext::new(cx).await;
 2073    cx.set_state(indoc! {"
 2074        // Fooˇ
 2075    "});
 2076    cx.update_editor(|e, cx| e.newline(&Newline, cx));
 2077    cx.assert_editor_state(indoc! {"
 2078        // Foo
 2079        ˇ
 2080    "});
 2081}
 2082
 2083#[gpui::test]
 2084fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2085    init_test(cx, |_| {});
 2086
 2087    let editor = cx.add_window(|cx| {
 2088        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2089        let mut editor = build_editor(buffer.clone(), cx);
 2090        editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
 2091        editor
 2092    });
 2093
 2094    _ = editor.update(cx, |editor, cx| {
 2095        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2096        editor.buffer.update(cx, |buffer, cx| {
 2097            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 2098            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 2099        });
 2100        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 2101
 2102        editor.insert("Z", cx);
 2103        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 2104
 2105        // The selections are moved after the inserted characters
 2106        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 2107    });
 2108}
 2109
 2110#[gpui::test]
 2111async fn test_tab(cx: &mut gpui::TestAppContext) {
 2112    init_test(cx, |settings| {
 2113        settings.defaults.tab_size = NonZeroU32::new(3)
 2114    });
 2115
 2116    let mut cx = EditorTestContext::new(cx).await;
 2117    cx.set_state(indoc! {"
 2118        ˇabˇc
 2119        ˇ🏀ˇ🏀ˇefg
 2120 2121    "});
 2122    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2123    cx.assert_editor_state(indoc! {"
 2124           ˇab ˇc
 2125           ˇ🏀  ˇ🏀  ˇefg
 2126        d  ˇ
 2127    "});
 2128
 2129    cx.set_state(indoc! {"
 2130        a
 2131        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2132    "});
 2133    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2134    cx.assert_editor_state(indoc! {"
 2135        a
 2136           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2137    "});
 2138}
 2139
 2140#[gpui::test]
 2141async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
 2142    init_test(cx, |_| {});
 2143
 2144    let mut cx = EditorTestContext::new(cx).await;
 2145    let language = Arc::new(
 2146        Language::new(
 2147            LanguageConfig::default(),
 2148            Some(tree_sitter_rust::language()),
 2149        )
 2150        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2151        .unwrap(),
 2152    );
 2153    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2154
 2155    // cursors that are already at the suggested indent level insert
 2156    // a soft tab. cursors that are to the left of the suggested indent
 2157    // auto-indent their line.
 2158    cx.set_state(indoc! {"
 2159        ˇ
 2160        const a: B = (
 2161            c(
 2162                d(
 2163        ˇ
 2164                )
 2165        ˇ
 2166        ˇ    )
 2167        );
 2168    "});
 2169    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2170    cx.assert_editor_state(indoc! {"
 2171            ˇ
 2172        const a: B = (
 2173            c(
 2174                d(
 2175                    ˇ
 2176                )
 2177                ˇ
 2178            ˇ)
 2179        );
 2180    "});
 2181
 2182    // handle auto-indent when there are multiple cursors on the same line
 2183    cx.set_state(indoc! {"
 2184        const a: B = (
 2185            c(
 2186        ˇ    ˇ
 2187        ˇ    )
 2188        );
 2189    "});
 2190    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2191    cx.assert_editor_state(indoc! {"
 2192        const a: B = (
 2193            c(
 2194                ˇ
 2195            ˇ)
 2196        );
 2197    "});
 2198}
 2199
 2200#[gpui::test]
 2201async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
 2202    init_test(cx, |settings| {
 2203        settings.defaults.tab_size = NonZeroU32::new(4)
 2204    });
 2205
 2206    let language = Arc::new(
 2207        Language::new(
 2208            LanguageConfig::default(),
 2209            Some(tree_sitter_rust::language()),
 2210        )
 2211        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 2212        .unwrap(),
 2213    );
 2214
 2215    let mut cx = EditorTestContext::new(cx).await;
 2216    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2217    cx.set_state(indoc! {"
 2218        fn a() {
 2219            if b {
 2220        \t ˇc
 2221            }
 2222        }
 2223    "});
 2224
 2225    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2226    cx.assert_editor_state(indoc! {"
 2227        fn a() {
 2228            if b {
 2229                ˇc
 2230            }
 2231        }
 2232    "});
 2233}
 2234
 2235#[gpui::test]
 2236async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
 2237    init_test(cx, |settings| {
 2238        settings.defaults.tab_size = NonZeroU32::new(4);
 2239    });
 2240
 2241    let mut cx = EditorTestContext::new(cx).await;
 2242
 2243    cx.set_state(indoc! {"
 2244          «oneˇ» «twoˇ»
 2245        three
 2246         four
 2247    "});
 2248    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2249    cx.assert_editor_state(indoc! {"
 2250            «oneˇ» «twoˇ»
 2251        three
 2252         four
 2253    "});
 2254
 2255    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2256    cx.assert_editor_state(indoc! {"
 2257        «oneˇ» «twoˇ»
 2258        three
 2259         four
 2260    "});
 2261
 2262    // select across line ending
 2263    cx.set_state(indoc! {"
 2264        one two
 2265        t«hree
 2266        ˇ» four
 2267    "});
 2268    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2269    cx.assert_editor_state(indoc! {"
 2270        one two
 2271            t«hree
 2272        ˇ» four
 2273    "});
 2274
 2275    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2276    cx.assert_editor_state(indoc! {"
 2277        one two
 2278        t«hree
 2279        ˇ» four
 2280    "});
 2281
 2282    // Ensure that indenting/outdenting works when the cursor is at column 0.
 2283    cx.set_state(indoc! {"
 2284        one two
 2285        ˇthree
 2286            four
 2287    "});
 2288    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2289    cx.assert_editor_state(indoc! {"
 2290        one two
 2291            ˇthree
 2292            four
 2293    "});
 2294
 2295    cx.set_state(indoc! {"
 2296        one two
 2297        ˇ    three
 2298            four
 2299    "});
 2300    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2301    cx.assert_editor_state(indoc! {"
 2302        one two
 2303        ˇthree
 2304            four
 2305    "});
 2306}
 2307
 2308#[gpui::test]
 2309async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
 2310    init_test(cx, |settings| {
 2311        settings.defaults.hard_tabs = Some(true);
 2312    });
 2313
 2314    let mut cx = EditorTestContext::new(cx).await;
 2315
 2316    // select two ranges on one line
 2317    cx.set_state(indoc! {"
 2318        «oneˇ» «twoˇ»
 2319        three
 2320        four
 2321    "});
 2322    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2323    cx.assert_editor_state(indoc! {"
 2324        \t«oneˇ» «twoˇ»
 2325        three
 2326        four
 2327    "});
 2328    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2329    cx.assert_editor_state(indoc! {"
 2330        \t\t«oneˇ» «twoˇ»
 2331        three
 2332        four
 2333    "});
 2334    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2335    cx.assert_editor_state(indoc! {"
 2336        \t«oneˇ» «twoˇ»
 2337        three
 2338        four
 2339    "});
 2340    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2341    cx.assert_editor_state(indoc! {"
 2342        «oneˇ» «twoˇ»
 2343        three
 2344        four
 2345    "});
 2346
 2347    // select across a line ending
 2348    cx.set_state(indoc! {"
 2349        one two
 2350        t«hree
 2351        ˇ»four
 2352    "});
 2353    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2354    cx.assert_editor_state(indoc! {"
 2355        one two
 2356        \tt«hree
 2357        ˇ»four
 2358    "});
 2359    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2360    cx.assert_editor_state(indoc! {"
 2361        one two
 2362        \t\tt«hree
 2363        ˇ»four
 2364    "});
 2365    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2366    cx.assert_editor_state(indoc! {"
 2367        one two
 2368        \tt«hree
 2369        ˇ»four
 2370    "});
 2371    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2372    cx.assert_editor_state(indoc! {"
 2373        one two
 2374        t«hree
 2375        ˇ»four
 2376    "});
 2377
 2378    // Ensure that indenting/outdenting works when the cursor is at column 0.
 2379    cx.set_state(indoc! {"
 2380        one two
 2381        ˇthree
 2382        four
 2383    "});
 2384    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2385    cx.assert_editor_state(indoc! {"
 2386        one two
 2387        ˇthree
 2388        four
 2389    "});
 2390    cx.update_editor(|e, cx| e.tab(&Tab, cx));
 2391    cx.assert_editor_state(indoc! {"
 2392        one two
 2393        \tˇthree
 2394        four
 2395    "});
 2396    cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
 2397    cx.assert_editor_state(indoc! {"
 2398        one two
 2399        ˇthree
 2400        four
 2401    "});
 2402}
 2403
 2404#[gpui::test]
 2405fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 2406    init_test(cx, |settings| {
 2407        settings.languages.extend([
 2408            (
 2409                "TOML".into(),
 2410                LanguageSettingsContent {
 2411                    tab_size: NonZeroU32::new(2),
 2412                    ..Default::default()
 2413                },
 2414            ),
 2415            (
 2416                "Rust".into(),
 2417                LanguageSettingsContent {
 2418                    tab_size: NonZeroU32::new(4),
 2419                    ..Default::default()
 2420                },
 2421            ),
 2422        ]);
 2423    });
 2424
 2425    let toml_language = Arc::new(Language::new(
 2426        LanguageConfig {
 2427            name: "TOML".into(),
 2428            ..Default::default()
 2429        },
 2430        None,
 2431    ));
 2432    let rust_language = Arc::new(Language::new(
 2433        LanguageConfig {
 2434            name: "Rust".into(),
 2435            ..Default::default()
 2436        },
 2437        None,
 2438    ));
 2439
 2440    let toml_buffer =
 2441        cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 2442    let rust_buffer = cx.new_model(|cx| {
 2443        Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
 2444    });
 2445    let multibuffer = cx.new_model(|cx| {
 2446        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 2447        multibuffer.push_excerpts(
 2448            toml_buffer.clone(),
 2449            [ExcerptRange {
 2450                context: Point::new(0, 0)..Point::new(2, 0),
 2451                primary: None,
 2452            }],
 2453            cx,
 2454        );
 2455        multibuffer.push_excerpts(
 2456            rust_buffer.clone(),
 2457            [ExcerptRange {
 2458                context: Point::new(0, 0)..Point::new(1, 0),
 2459                primary: None,
 2460            }],
 2461            cx,
 2462        );
 2463        multibuffer
 2464    });
 2465
 2466    cx.add_window(|cx| {
 2467        let mut editor = build_editor(multibuffer, cx);
 2468
 2469        assert_eq!(
 2470            editor.text(cx),
 2471            indoc! {"
 2472                a = 1
 2473                b = 2
 2474
 2475                const c: usize = 3;
 2476            "}
 2477        );
 2478
 2479        select_ranges(
 2480            &mut editor,
 2481            indoc! {"
 2482                «aˇ» = 1
 2483                b = 2
 2484
 2485                «const c:ˇ» usize = 3;
 2486            "},
 2487            cx,
 2488        );
 2489
 2490        editor.tab(&Tab, cx);
 2491        assert_text_with_selections(
 2492            &mut editor,
 2493            indoc! {"
 2494                  «aˇ» = 1
 2495                b = 2
 2496
 2497                    «const c:ˇ» usize = 3;
 2498            "},
 2499            cx,
 2500        );
 2501        editor.tab_prev(&TabPrev, cx);
 2502        assert_text_with_selections(
 2503            &mut editor,
 2504            indoc! {"
 2505                «aˇ» = 1
 2506                b = 2
 2507
 2508                «const c:ˇ» usize = 3;
 2509            "},
 2510            cx,
 2511        );
 2512
 2513        editor
 2514    });
 2515}
 2516
 2517#[gpui::test]
 2518async fn test_backspace(cx: &mut gpui::TestAppContext) {
 2519    init_test(cx, |_| {});
 2520
 2521    let mut cx = EditorTestContext::new(cx).await;
 2522
 2523    // Basic backspace
 2524    cx.set_state(indoc! {"
 2525        onˇe two three
 2526        fou«rˇ» five six
 2527        seven «ˇeight nine
 2528        »ten
 2529    "});
 2530    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2531    cx.assert_editor_state(indoc! {"
 2532        oˇe two three
 2533        fouˇ five six
 2534        seven ˇten
 2535    "});
 2536
 2537    // Test backspace inside and around indents
 2538    cx.set_state(indoc! {"
 2539        zero
 2540            ˇone
 2541                ˇtwo
 2542            ˇ ˇ ˇ  three
 2543        ˇ  ˇ  four
 2544    "});
 2545    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2546    cx.assert_editor_state(indoc! {"
 2547        zero
 2548        ˇone
 2549            ˇtwo
 2550        ˇ  threeˇ  four
 2551    "});
 2552
 2553    // Test backspace with line_mode set to true
 2554    cx.update_editor(|e, _| e.selections.line_mode = true);
 2555    cx.set_state(indoc! {"
 2556        The ˇquick ˇbrown
 2557        fox jumps over
 2558        the lazy dog
 2559        ˇThe qu«ick bˇ»rown"});
 2560    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2561    cx.assert_editor_state(indoc! {"
 2562        ˇfox jumps over
 2563        the lazy dogˇ"});
 2564}
 2565
 2566#[gpui::test]
 2567async fn test_delete(cx: &mut gpui::TestAppContext) {
 2568    init_test(cx, |_| {});
 2569
 2570    let mut cx = EditorTestContext::new(cx).await;
 2571    cx.set_state(indoc! {"
 2572        onˇe two three
 2573        fou«rˇ» five six
 2574        seven «ˇeight nine
 2575        »ten
 2576    "});
 2577    cx.update_editor(|e, cx| e.delete(&Delete, cx));
 2578    cx.assert_editor_state(indoc! {"
 2579        onˇ two three
 2580        fouˇ five six
 2581        seven ˇten
 2582    "});
 2583
 2584    // Test backspace with line_mode set to true
 2585    cx.update_editor(|e, _| e.selections.line_mode = true);
 2586    cx.set_state(indoc! {"
 2587        The ˇquick ˇbrown
 2588        fox «ˇjum»ps over
 2589        the lazy dog
 2590        ˇThe qu«ick bˇ»rown"});
 2591    cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
 2592    cx.assert_editor_state("ˇthe lazy dogˇ");
 2593}
 2594
 2595#[gpui::test]
 2596fn test_delete_line(cx: &mut TestAppContext) {
 2597    init_test(cx, |_| {});
 2598
 2599    let view = cx.add_window(|cx| {
 2600        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 2601        build_editor(buffer, cx)
 2602    });
 2603    _ = view.update(cx, |view, cx| {
 2604        view.change_selections(None, cx, |s| {
 2605            s.select_display_ranges([
 2606                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 2607                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 2608                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 2609            ])
 2610        });
 2611        view.delete_line(&DeleteLine, cx);
 2612        assert_eq!(view.display_text(cx), "ghi");
 2613        assert_eq!(
 2614            view.selections.display_ranges(cx),
 2615            vec![
 2616                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
 2617                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
 2618            ]
 2619        );
 2620    });
 2621
 2622    let view = cx.add_window(|cx| {
 2623        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 2624        build_editor(buffer, cx)
 2625    });
 2626    _ = view.update(cx, |view, cx| {
 2627        view.change_selections(None, cx, |s| {
 2628            s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
 2629        });
 2630        view.delete_line(&DeleteLine, cx);
 2631        assert_eq!(view.display_text(cx), "ghi\n");
 2632        assert_eq!(
 2633            view.selections.display_ranges(cx),
 2634            vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
 2635        );
 2636    });
 2637}
 2638
 2639#[gpui::test]
 2640fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 2641    init_test(cx, |_| {});
 2642
 2643    cx.add_window(|cx| {
 2644        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 2645        let mut editor = build_editor(buffer.clone(), cx);
 2646        let buffer = buffer.read(cx).as_singleton().unwrap();
 2647
 2648        assert_eq!(
 2649            editor.selections.ranges::<Point>(cx),
 2650            &[Point::new(0, 0)..Point::new(0, 0)]
 2651        );
 2652
 2653        // When on single line, replace newline at end by space
 2654        editor.join_lines(&JoinLines, cx);
 2655        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 2656        assert_eq!(
 2657            editor.selections.ranges::<Point>(cx),
 2658            &[Point::new(0, 3)..Point::new(0, 3)]
 2659        );
 2660
 2661        // When multiple lines are selected, remove newlines that are spanned by the selection
 2662        editor.change_selections(None, cx, |s| {
 2663            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 2664        });
 2665        editor.join_lines(&JoinLines, cx);
 2666        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 2667        assert_eq!(
 2668            editor.selections.ranges::<Point>(cx),
 2669            &[Point::new(0, 11)..Point::new(0, 11)]
 2670        );
 2671
 2672        // Undo should be transactional
 2673        editor.undo(&Undo, cx);
 2674        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 2675        assert_eq!(
 2676            editor.selections.ranges::<Point>(cx),
 2677            &[Point::new(0, 5)..Point::new(2, 2)]
 2678        );
 2679
 2680        // When joining an empty line don't insert a space
 2681        editor.change_selections(None, cx, |s| {
 2682            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 2683        });
 2684        editor.join_lines(&JoinLines, cx);
 2685        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 2686        assert_eq!(
 2687            editor.selections.ranges::<Point>(cx),
 2688            [Point::new(2, 3)..Point::new(2, 3)]
 2689        );
 2690
 2691        // We can remove trailing newlines
 2692        editor.join_lines(&JoinLines, cx);
 2693        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 2694        assert_eq!(
 2695            editor.selections.ranges::<Point>(cx),
 2696            [Point::new(2, 3)..Point::new(2, 3)]
 2697        );
 2698
 2699        // We don't blow up on the last line
 2700        editor.join_lines(&JoinLines, cx);
 2701        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 2702        assert_eq!(
 2703            editor.selections.ranges::<Point>(cx),
 2704            [Point::new(2, 3)..Point::new(2, 3)]
 2705        );
 2706
 2707        // reset to test indentation
 2708        editor.buffer.update(cx, |buffer, cx| {
 2709            buffer.edit(
 2710                [
 2711                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 2712                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 2713                ],
 2714                None,
 2715                cx,
 2716            )
 2717        });
 2718
 2719        // We remove any leading spaces
 2720        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 2721        editor.change_selections(None, cx, |s| {
 2722            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 2723        });
 2724        editor.join_lines(&JoinLines, cx);
 2725        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 2726
 2727        // We don't insert a space for a line containing only spaces
 2728        editor.join_lines(&JoinLines, cx);
 2729        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 2730
 2731        // We ignore any leading tabs
 2732        editor.join_lines(&JoinLines, cx);
 2733        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 2734
 2735        editor
 2736    });
 2737}
 2738
 2739#[gpui::test]
 2740fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 2741    init_test(cx, |_| {});
 2742
 2743    cx.add_window(|cx| {
 2744        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 2745        let mut editor = build_editor(buffer.clone(), cx);
 2746        let buffer = buffer.read(cx).as_singleton().unwrap();
 2747
 2748        editor.change_selections(None, cx, |s| {
 2749            s.select_ranges([
 2750                Point::new(0, 2)..Point::new(1, 1),
 2751                Point::new(1, 2)..Point::new(1, 2),
 2752                Point::new(3, 1)..Point::new(3, 2),
 2753            ])
 2754        });
 2755
 2756        editor.join_lines(&JoinLines, cx);
 2757        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 2758
 2759        assert_eq!(
 2760            editor.selections.ranges::<Point>(cx),
 2761            [
 2762                Point::new(0, 7)..Point::new(0, 7),
 2763                Point::new(1, 3)..Point::new(1, 3)
 2764            ]
 2765        );
 2766        editor
 2767    });
 2768}
 2769
 2770#[gpui::test]
 2771async fn test_join_lines_with_git_diff_base(
 2772    executor: BackgroundExecutor,
 2773    cx: &mut gpui::TestAppContext,
 2774) {
 2775    init_test(cx, |_| {});
 2776
 2777    let mut cx = EditorTestContext::new(cx).await;
 2778
 2779    let diff_base = r#"
 2780        Line 0
 2781        Line 1
 2782        Line 2
 2783        Line 3
 2784        "#
 2785    .unindent();
 2786
 2787    cx.set_state(
 2788        &r#"
 2789        ˇLine 0
 2790        Line 1
 2791        Line 2
 2792        Line 3
 2793        "#
 2794        .unindent(),
 2795    );
 2796
 2797    cx.set_diff_base(Some(&diff_base));
 2798    executor.run_until_parked();
 2799
 2800    // Join lines
 2801    cx.update_editor(|editor, cx| {
 2802        editor.join_lines(&JoinLines, cx);
 2803    });
 2804    executor.run_until_parked();
 2805
 2806    cx.assert_editor_state(
 2807        &r#"
 2808        Line 0ˇ Line 1
 2809        Line 2
 2810        Line 3
 2811        "#
 2812        .unindent(),
 2813    );
 2814    // Join again
 2815    cx.update_editor(|editor, cx| {
 2816        editor.join_lines(&JoinLines, cx);
 2817    });
 2818    executor.run_until_parked();
 2819
 2820    cx.assert_editor_state(
 2821        &r#"
 2822        Line 0 Line 1ˇ Line 2
 2823        Line 3
 2824        "#
 2825        .unindent(),
 2826    );
 2827}
 2828
 2829#[gpui::test]
 2830async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 2831    init_test(cx, |_| {});
 2832
 2833    let mut cx = EditorTestContext::new(cx).await;
 2834
 2835    // Test sort_lines_case_insensitive()
 2836    cx.set_state(indoc! {"
 2837        «z
 2838        y
 2839        x
 2840        Z
 2841        Y
 2842        Xˇ»
 2843    "});
 2844    cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
 2845    cx.assert_editor_state(indoc! {"
 2846        «x
 2847        X
 2848        y
 2849        Y
 2850        z
 2851        Zˇ»
 2852    "});
 2853
 2854    // Test reverse_lines()
 2855    cx.set_state(indoc! {"
 2856        «5
 2857        4
 2858        3
 2859        2
 2860        1ˇ»
 2861    "});
 2862    cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
 2863    cx.assert_editor_state(indoc! {"
 2864        «1
 2865        2
 2866        3
 2867        4
 2868        5ˇ»
 2869    "});
 2870
 2871    // Skip testing shuffle_line()
 2872
 2873    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 2874    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 2875
 2876    // Don't manipulate when cursor is on single line, but expand the selection
 2877    cx.set_state(indoc! {"
 2878        ddˇdd
 2879        ccc
 2880        bb
 2881        a
 2882    "});
 2883    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 2884    cx.assert_editor_state(indoc! {"
 2885        «ddddˇ»
 2886        ccc
 2887        bb
 2888        a
 2889    "});
 2890
 2891    // Basic manipulate case
 2892    // Start selection moves to column 0
 2893    // End of selection shrinks to fit shorter line
 2894    cx.set_state(indoc! {"
 2895        dd«d
 2896        ccc
 2897        bb
 2898        aaaaaˇ»
 2899    "});
 2900    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 2901    cx.assert_editor_state(indoc! {"
 2902        «aaaaa
 2903        bb
 2904        ccc
 2905        dddˇ»
 2906    "});
 2907
 2908    // Manipulate case with newlines
 2909    cx.set_state(indoc! {"
 2910        dd«d
 2911        ccc
 2912
 2913        bb
 2914        aaaaa
 2915
 2916        ˇ»
 2917    "});
 2918    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 2919    cx.assert_editor_state(indoc! {"
 2920        «
 2921
 2922        aaaaa
 2923        bb
 2924        ccc
 2925        dddˇ»
 2926
 2927    "});
 2928
 2929    // Adding new line
 2930    cx.set_state(indoc! {"
 2931        aa«a
 2932        bbˇ»b
 2933    "});
 2934    cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
 2935    cx.assert_editor_state(indoc! {"
 2936        «aaa
 2937        bbb
 2938        added_lineˇ»
 2939    "});
 2940
 2941    // Removing line
 2942    cx.set_state(indoc! {"
 2943        aa«a
 2944        bbbˇ»
 2945    "});
 2946    cx.update_editor(|e, cx| {
 2947        e.manipulate_lines(cx, |lines| {
 2948            lines.pop();
 2949        })
 2950    });
 2951    cx.assert_editor_state(indoc! {"
 2952        «aaaˇ»
 2953    "});
 2954
 2955    // Removing all lines
 2956    cx.set_state(indoc! {"
 2957        aa«a
 2958        bbbˇ»
 2959    "});
 2960    cx.update_editor(|e, cx| {
 2961        e.manipulate_lines(cx, |lines| {
 2962            lines.drain(..);
 2963        })
 2964    });
 2965    cx.assert_editor_state(indoc! {"
 2966        ˇ
 2967    "});
 2968}
 2969
 2970#[gpui::test]
 2971async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 2972    init_test(cx, |_| {});
 2973
 2974    let mut cx = EditorTestContext::new(cx).await;
 2975
 2976    // Consider continuous selection as single selection
 2977    cx.set_state(indoc! {"
 2978        Aaa«aa
 2979        cˇ»c«c
 2980        bb
 2981        aaaˇ»aa
 2982    "});
 2983    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
 2984    cx.assert_editor_state(indoc! {"
 2985        «Aaaaa
 2986        ccc
 2987        bb
 2988        aaaaaˇ»
 2989    "});
 2990
 2991    cx.set_state(indoc! {"
 2992        Aaa«aa
 2993        cˇ»c«c
 2994        bb
 2995        aaaˇ»aa
 2996    "});
 2997    cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
 2998    cx.assert_editor_state(indoc! {"
 2999        «Aaaaa
 3000        ccc
 3001        bbˇ»
 3002    "});
 3003
 3004    // Consider non continuous selection as distinct dedup operations
 3005    cx.set_state(indoc! {"
 3006        «aaaaa
 3007        bb
 3008        aaaaa
 3009        aaaaaˇ»
 3010
 3011        aaa«aaˇ»
 3012    "});
 3013    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
 3014    cx.assert_editor_state(indoc! {"
 3015        «aaaaa
 3016        bbˇ»
 3017
 3018        «aaaaaˇ»
 3019    "});
 3020}
 3021
 3022#[gpui::test]
 3023async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 3024    init_test(cx, |_| {});
 3025
 3026    let mut cx = EditorTestContext::new(cx).await;
 3027
 3028    cx.set_state(indoc! {"
 3029        «Aaa
 3030        aAa
 3031        Aaaˇ»
 3032    "});
 3033    cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
 3034    cx.assert_editor_state(indoc! {"
 3035        «Aaa
 3036        aAaˇ»
 3037    "});
 3038
 3039    cx.set_state(indoc! {"
 3040        «Aaa
 3041        aAa
 3042        aaAˇ»
 3043    "});
 3044    cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
 3045    cx.assert_editor_state(indoc! {"
 3046        «Aaaˇ»
 3047    "});
 3048}
 3049
 3050#[gpui::test]
 3051async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 3052    init_test(cx, |_| {});
 3053
 3054    let mut cx = EditorTestContext::new(cx).await;
 3055
 3056    // Manipulate with multiple selections on a single line
 3057    cx.set_state(indoc! {"
 3058        dd«dd
 3059        cˇ»c«c
 3060        bb
 3061        aaaˇ»aa
 3062    "});
 3063    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3064    cx.assert_editor_state(indoc! {"
 3065        «aaaaa
 3066        bb
 3067        ccc
 3068        ddddˇ»
 3069    "});
 3070
 3071    // Manipulate with multiple disjoin selections
 3072    cx.set_state(indoc! {"
 3073 3074        4
 3075        3
 3076        2
 3077        1ˇ»
 3078
 3079        dd«dd
 3080        ccc
 3081        bb
 3082        aaaˇ»aa
 3083    "});
 3084    cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
 3085    cx.assert_editor_state(indoc! {"
 3086        «1
 3087        2
 3088        3
 3089        4
 3090        5ˇ»
 3091
 3092        «aaaaa
 3093        bb
 3094        ccc
 3095        ddddˇ»
 3096    "});
 3097
 3098    // Adding lines on each selection
 3099    cx.set_state(indoc! {"
 3100 3101        1ˇ»
 3102
 3103        bb«bb
 3104        aaaˇ»aa
 3105    "});
 3106    cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
 3107    cx.assert_editor_state(indoc! {"
 3108        «2
 3109        1
 3110        added lineˇ»
 3111
 3112        «bbbb
 3113        aaaaa
 3114        added lineˇ»
 3115    "});
 3116
 3117    // Removing lines on each selection
 3118    cx.set_state(indoc! {"
 3119 3120        1ˇ»
 3121
 3122        bb«bb
 3123        aaaˇ»aa
 3124    "});
 3125    cx.update_editor(|e, cx| {
 3126        e.manipulate_lines(cx, |lines| {
 3127            lines.pop();
 3128        })
 3129    });
 3130    cx.assert_editor_state(indoc! {"
 3131        «2ˇ»
 3132
 3133        «bbbbˇ»
 3134    "});
 3135}
 3136
 3137#[gpui::test]
 3138async fn test_manipulate_text(cx: &mut TestAppContext) {
 3139    init_test(cx, |_| {});
 3140
 3141    let mut cx = EditorTestContext::new(cx).await;
 3142
 3143    // Test convert_to_upper_case()
 3144    cx.set_state(indoc! {"
 3145        «hello worldˇ»
 3146    "});
 3147    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3148    cx.assert_editor_state(indoc! {"
 3149        «HELLO WORLDˇ»
 3150    "});
 3151
 3152    // Test convert_to_lower_case()
 3153    cx.set_state(indoc! {"
 3154        «HELLO WORLDˇ»
 3155    "});
 3156    cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
 3157    cx.assert_editor_state(indoc! {"
 3158        «hello worldˇ»
 3159    "});
 3160
 3161    // Test multiple line, single selection case
 3162    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
 3163    cx.set_state(indoc! {"
 3164        «The quick brown
 3165        fox jumps over
 3166        the lazy dogˇ»
 3167    "});
 3168    cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
 3169    cx.assert_editor_state(indoc! {"
 3170        «The Quick Brown
 3171        Fox Jumps Over
 3172        The Lazy Dogˇ»
 3173    "});
 3174
 3175    // Test multiple line, single selection case
 3176    // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
 3177    cx.set_state(indoc! {"
 3178        «The quick brown
 3179        fox jumps over
 3180        the lazy dogˇ»
 3181    "});
 3182    cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
 3183    cx.assert_editor_state(indoc! {"
 3184        «TheQuickBrown
 3185        FoxJumpsOver
 3186        TheLazyDogˇ»
 3187    "});
 3188
 3189    // From here on out, test more complex cases of manipulate_text()
 3190
 3191    // Test no selection case - should affect words cursors are in
 3192    // Cursor at beginning, middle, and end of word
 3193    cx.set_state(indoc! {"
 3194        ˇhello big beauˇtiful worldˇ
 3195    "});
 3196    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3197    cx.assert_editor_state(indoc! {"
 3198        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 3199    "});
 3200
 3201    // Test multiple selections on a single line and across multiple lines
 3202    cx.set_state(indoc! {"
 3203        «Theˇ» quick «brown
 3204        foxˇ» jumps «overˇ»
 3205        the «lazyˇ» dog
 3206    "});
 3207    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3208    cx.assert_editor_state(indoc! {"
 3209        «THEˇ» quick «BROWN
 3210        FOXˇ» jumps «OVERˇ»
 3211        the «LAZYˇ» dog
 3212    "});
 3213
 3214    // Test case where text length grows
 3215    cx.set_state(indoc! {"
 3216        «tschüߡ»
 3217    "});
 3218    cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
 3219    cx.assert_editor_state(indoc! {"
 3220        «TSCHÜSSˇ»
 3221    "});
 3222
 3223    // Test to make sure we don't crash when text shrinks
 3224    cx.set_state(indoc! {"
 3225        aaa_bbbˇ
 3226    "});
 3227    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        «aaaBbbˇ»
 3230    "});
 3231
 3232    // Test to make sure we all aware of the fact that each word can grow and shrink
 3233    // Final selections should be aware of this fact
 3234    cx.set_state(indoc! {"
 3235        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 3236    "});
 3237    cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
 3238    cx.assert_editor_state(indoc! {"
 3239        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 3240    "});
 3241
 3242    cx.set_state(indoc! {"
 3243        «hElLo, WoRld!ˇ»
 3244    "});
 3245    cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
 3246    cx.assert_editor_state(indoc! {"
 3247        «HeLlO, wOrLD!ˇ»
 3248    "});
 3249}
 3250
 3251#[gpui::test]
 3252fn test_duplicate_line(cx: &mut TestAppContext) {
 3253    init_test(cx, |_| {});
 3254
 3255    let view = cx.add_window(|cx| {
 3256        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3257        build_editor(buffer, cx)
 3258    });
 3259    _ = view.update(cx, |view, cx| {
 3260        view.change_selections(None, cx, |s| {
 3261            s.select_display_ranges([
 3262                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3263                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3264                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3265                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 3266            ])
 3267        });
 3268        view.duplicate_line_down(&DuplicateLineDown, cx);
 3269        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 3270        assert_eq!(
 3271            view.selections.display_ranges(cx),
 3272            vec![
 3273                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 3274                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
 3275                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 3276                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
 3277            ]
 3278        );
 3279    });
 3280
 3281    let view = cx.add_window(|cx| {
 3282        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3283        build_editor(buffer, cx)
 3284    });
 3285    _ = view.update(cx, |view, cx| {
 3286        view.change_selections(None, cx, |s| {
 3287            s.select_display_ranges([
 3288                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
 3289                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
 3290            ])
 3291        });
 3292        view.duplicate_line_down(&DuplicateLineDown, cx);
 3293        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 3294        assert_eq!(
 3295            view.selections.display_ranges(cx),
 3296            vec![
 3297                DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
 3298                DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
 3299            ]
 3300        );
 3301    });
 3302
 3303    // With `move_upwards` the selections stay in place, except for
 3304    // the lines inserted above them
 3305    let view = cx.add_window(|cx| {
 3306        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3307        build_editor(buffer, cx)
 3308    });
 3309    _ = view.update(cx, |view, cx| {
 3310        view.change_selections(None, cx, |s| {
 3311            s.select_display_ranges([
 3312                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3313                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3314                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3315                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 3316            ])
 3317        });
 3318        view.duplicate_line_up(&DuplicateLineUp, cx);
 3319        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 3320        assert_eq!(
 3321            view.selections.display_ranges(cx),
 3322            vec![
 3323                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3324                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3325                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
 3326                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
 3327            ]
 3328        );
 3329    });
 3330
 3331    let view = cx.add_window(|cx| {
 3332        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3333        build_editor(buffer, cx)
 3334    });
 3335    _ = view.update(cx, |view, cx| {
 3336        view.change_selections(None, cx, |s| {
 3337            s.select_display_ranges([
 3338                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
 3339                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
 3340            ])
 3341        });
 3342        view.duplicate_line_up(&DuplicateLineUp, cx);
 3343        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 3344        assert_eq!(
 3345            view.selections.display_ranges(cx),
 3346            vec![
 3347                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
 3348                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
 3349            ]
 3350        );
 3351    });
 3352}
 3353
 3354#[gpui::test]
 3355fn test_move_line_up_down(cx: &mut TestAppContext) {
 3356    init_test(cx, |_| {});
 3357
 3358    let view = cx.add_window(|cx| {
 3359        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 3360        build_editor(buffer, cx)
 3361    });
 3362    _ = view.update(cx, |view, cx| {
 3363        view.fold_ranges(
 3364            vec![
 3365                Point::new(0, 2)..Point::new(1, 2),
 3366                Point::new(2, 3)..Point::new(4, 1),
 3367                Point::new(7, 0)..Point::new(8, 4),
 3368            ],
 3369            true,
 3370            cx,
 3371        );
 3372        view.change_selections(None, cx, |s| {
 3373            s.select_display_ranges([
 3374                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 3375                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
 3376                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
 3377                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
 3378            ])
 3379        });
 3380        assert_eq!(
 3381            view.display_text(cx),
 3382            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 3383        );
 3384
 3385        view.move_line_up(&MoveLineUp, cx);
 3386        assert_eq!(
 3387            view.display_text(cx),
 3388            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 3389        );
 3390        assert_eq!(
 3391            view.selections.display_ranges(cx),
 3392            vec![
 3393                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 3394                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
 3395                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
 3396                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
 3397            ]
 3398        );
 3399    });
 3400
 3401    _ = view.update(cx, |view, cx| {
 3402        view.move_line_down(&MoveLineDown, cx);
 3403        assert_eq!(
 3404            view.display_text(cx),
 3405            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 3406        );
 3407        assert_eq!(
 3408            view.selections.display_ranges(cx),
 3409            vec![
 3410                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 3411                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
 3412                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
 3413                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
 3414            ]
 3415        );
 3416    });
 3417
 3418    _ = view.update(cx, |view, cx| {
 3419        view.move_line_down(&MoveLineDown, cx);
 3420        assert_eq!(
 3421            view.display_text(cx),
 3422            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 3423        );
 3424        assert_eq!(
 3425            view.selections.display_ranges(cx),
 3426            vec![
 3427                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
 3428                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
 3429                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
 3430                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
 3431            ]
 3432        );
 3433    });
 3434
 3435    _ = view.update(cx, |view, cx| {
 3436        view.move_line_up(&MoveLineUp, cx);
 3437        assert_eq!(
 3438            view.display_text(cx),
 3439            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 3440        );
 3441        assert_eq!(
 3442            view.selections.display_ranges(cx),
 3443            vec![
 3444                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 3445                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
 3446                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
 3447                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
 3448            ]
 3449        );
 3450    });
 3451}
 3452
 3453#[gpui::test]
 3454fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 3455    init_test(cx, |_| {});
 3456
 3457    let editor = cx.add_window(|cx| {
 3458        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 3459        build_editor(buffer, cx)
 3460    });
 3461    _ = editor.update(cx, |editor, cx| {
 3462        let snapshot = editor.buffer.read(cx).snapshot(cx);
 3463        editor.insert_blocks(
 3464            [BlockProperties {
 3465                style: BlockStyle::Fixed,
 3466                position: snapshot.anchor_after(Point::new(2, 0)),
 3467                disposition: BlockDisposition::Below,
 3468                height: 1,
 3469                render: Box::new(|_| div().into_any()),
 3470            }],
 3471            Some(Autoscroll::fit()),
 3472            cx,
 3473        );
 3474        editor.change_selections(None, cx, |s| {
 3475            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 3476        });
 3477        editor.move_line_down(&MoveLineDown, cx);
 3478    });
 3479}
 3480
 3481#[gpui::test]
 3482fn test_transpose(cx: &mut TestAppContext) {
 3483    init_test(cx, |_| {});
 3484
 3485    _ = cx.add_window(|cx| {
 3486        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
 3487        editor.set_style(EditorStyle::default(), cx);
 3488        editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
 3489        editor.transpose(&Default::default(), cx);
 3490        assert_eq!(editor.text(cx), "bac");
 3491        assert_eq!(editor.selections.ranges(cx), [2..2]);
 3492
 3493        editor.transpose(&Default::default(), cx);
 3494        assert_eq!(editor.text(cx), "bca");
 3495        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3496
 3497        editor.transpose(&Default::default(), cx);
 3498        assert_eq!(editor.text(cx), "bac");
 3499        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3500
 3501        editor
 3502    });
 3503
 3504    _ = cx.add_window(|cx| {
 3505        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 3506        editor.set_style(EditorStyle::default(), cx);
 3507        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
 3508        editor.transpose(&Default::default(), cx);
 3509        assert_eq!(editor.text(cx), "acb\nde");
 3510        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3511
 3512        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
 3513        editor.transpose(&Default::default(), cx);
 3514        assert_eq!(editor.text(cx), "acbd\ne");
 3515        assert_eq!(editor.selections.ranges(cx), [5..5]);
 3516
 3517        editor.transpose(&Default::default(), cx);
 3518        assert_eq!(editor.text(cx), "acbde\n");
 3519        assert_eq!(editor.selections.ranges(cx), [6..6]);
 3520
 3521        editor.transpose(&Default::default(), cx);
 3522        assert_eq!(editor.text(cx), "acbd\ne");
 3523        assert_eq!(editor.selections.ranges(cx), [6..6]);
 3524
 3525        editor
 3526    });
 3527
 3528    _ = cx.add_window(|cx| {
 3529        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 3530        editor.set_style(EditorStyle::default(), cx);
 3531        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 3532        editor.transpose(&Default::default(), cx);
 3533        assert_eq!(editor.text(cx), "bacd\ne");
 3534        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 3535
 3536        editor.transpose(&Default::default(), cx);
 3537        assert_eq!(editor.text(cx), "bcade\n");
 3538        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 3539
 3540        editor.transpose(&Default::default(), cx);
 3541        assert_eq!(editor.text(cx), "bcda\ne");
 3542        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 3543
 3544        editor.transpose(&Default::default(), cx);
 3545        assert_eq!(editor.text(cx), "bcade\n");
 3546        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 3547
 3548        editor.transpose(&Default::default(), cx);
 3549        assert_eq!(editor.text(cx), "bcaed\n");
 3550        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 3551
 3552        editor
 3553    });
 3554
 3555    _ = cx.add_window(|cx| {
 3556        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
 3557        editor.set_style(EditorStyle::default(), cx);
 3558        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
 3559        editor.transpose(&Default::default(), cx);
 3560        assert_eq!(editor.text(cx), "🏀🍐✋");
 3561        assert_eq!(editor.selections.ranges(cx), [8..8]);
 3562
 3563        editor.transpose(&Default::default(), cx);
 3564        assert_eq!(editor.text(cx), "🏀✋🍐");
 3565        assert_eq!(editor.selections.ranges(cx), [11..11]);
 3566
 3567        editor.transpose(&Default::default(), cx);
 3568        assert_eq!(editor.text(cx), "🏀🍐✋");
 3569        assert_eq!(editor.selections.ranges(cx), [11..11]);
 3570
 3571        editor
 3572    });
 3573}
 3574
 3575#[gpui::test]
 3576async fn test_clipboard(cx: &mut gpui::TestAppContext) {
 3577    init_test(cx, |_| {});
 3578
 3579    let mut cx = EditorTestContext::new(cx).await;
 3580
 3581    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 3582    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3583    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 3584
 3585    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 3586    cx.set_state("two ˇfour ˇsix ˇ");
 3587    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3588    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 3589
 3590    // Paste again but with only two cursors. Since the number of cursors doesn't
 3591    // match the number of slices in the clipboard, the entire clipboard text
 3592    // is pasted at each cursor.
 3593    cx.set_state("ˇtwo one✅ four three six five ˇ");
 3594    cx.update_editor(|e, cx| {
 3595        e.handle_input("( ", cx);
 3596        e.paste(&Paste, cx);
 3597        e.handle_input(") ", cx);
 3598    });
 3599    cx.assert_editor_state(
 3600        &([
 3601            "( one✅ ",
 3602            "three ",
 3603            "five ) ˇtwo one✅ four three six five ( one✅ ",
 3604            "three ",
 3605            "five ) ˇ",
 3606        ]
 3607        .join("\n")),
 3608    );
 3609
 3610    // Cut with three selections, one of which is full-line.
 3611    cx.set_state(indoc! {"
 3612        1«2ˇ»3
 3613        4ˇ567
 3614        «8ˇ»9"});
 3615    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3616    cx.assert_editor_state(indoc! {"
 3617        1ˇ3
 3618        ˇ9"});
 3619
 3620    // Paste with three selections, noticing how the copied selection that was full-line
 3621    // gets inserted before the second cursor.
 3622    cx.set_state(indoc! {"
 3623        1ˇ3
 3624 3625        «oˇ»ne"});
 3626    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3627    cx.assert_editor_state(indoc! {"
 3628        12ˇ3
 3629        4567
 3630 3631        8ˇne"});
 3632
 3633    // Copy with a single cursor only, which writes the whole line into the clipboard.
 3634    cx.set_state(indoc! {"
 3635        The quick brown
 3636        fox juˇmps over
 3637        the lazy dog"});
 3638    cx.update_editor(|e, cx| e.copy(&Copy, cx));
 3639    assert_eq!(
 3640        cx.read_from_clipboard().map(|item| item.text().to_owned()),
 3641        Some("fox jumps over\n".to_owned())
 3642    );
 3643
 3644    // Paste with three selections, noticing how the copied full-line selection is inserted
 3645    // before the empty selections but replaces the selection that is non-empty.
 3646    cx.set_state(indoc! {"
 3647        Tˇhe quick brown
 3648        «foˇ»x jumps over
 3649        tˇhe lazy dog"});
 3650    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        fox jumps over
 3653        Tˇhe quick brown
 3654        fox jumps over
 3655        ˇx jumps over
 3656        fox jumps over
 3657        tˇhe lazy dog"});
 3658}
 3659
 3660#[gpui::test]
 3661async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 3662    init_test(cx, |_| {});
 3663
 3664    let mut cx = EditorTestContext::new(cx).await;
 3665    let language = Arc::new(Language::new(
 3666        LanguageConfig::default(),
 3667        Some(tree_sitter_rust::language()),
 3668    ));
 3669    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3670
 3671    // Cut an indented block, without the leading whitespace.
 3672    cx.set_state(indoc! {"
 3673        const a: B = (
 3674            c(),
 3675            «d(
 3676                e,
 3677                f
 3678            )ˇ»
 3679        );
 3680    "});
 3681    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3682    cx.assert_editor_state(indoc! {"
 3683        const a: B = (
 3684            c(),
 3685            ˇ
 3686        );
 3687    "});
 3688
 3689    // Paste it at the same position.
 3690    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3691    cx.assert_editor_state(indoc! {"
 3692        const a: B = (
 3693            c(),
 3694            d(
 3695                e,
 3696                f
 3697 3698        );
 3699    "});
 3700
 3701    // Paste it at a line with a lower indent level.
 3702    cx.set_state(indoc! {"
 3703        ˇ
 3704        const a: B = (
 3705            c(),
 3706        );
 3707    "});
 3708    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3709    cx.assert_editor_state(indoc! {"
 3710        d(
 3711            e,
 3712            f
 3713 3714        const a: B = (
 3715            c(),
 3716        );
 3717    "});
 3718
 3719    // Cut an indented block, with the leading whitespace.
 3720    cx.set_state(indoc! {"
 3721        const a: B = (
 3722            c(),
 3723        «    d(
 3724                e,
 3725                f
 3726            )
 3727        ˇ»);
 3728    "});
 3729    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3730    cx.assert_editor_state(indoc! {"
 3731        const a: B = (
 3732            c(),
 3733        ˇ);
 3734    "});
 3735
 3736    // Paste it at the same position.
 3737    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3738    cx.assert_editor_state(indoc! {"
 3739        const a: B = (
 3740            c(),
 3741            d(
 3742                e,
 3743                f
 3744            )
 3745        ˇ);
 3746    "});
 3747
 3748    // Paste it at a line with a higher indent level.
 3749    cx.set_state(indoc! {"
 3750        const a: B = (
 3751            c(),
 3752            d(
 3753                e,
 3754 3755            )
 3756        );
 3757    "});
 3758    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3759    cx.assert_editor_state(indoc! {"
 3760        const a: B = (
 3761            c(),
 3762            d(
 3763                e,
 3764                f    d(
 3765                    e,
 3766                    f
 3767                )
 3768        ˇ
 3769            )
 3770        );
 3771    "});
 3772}
 3773
 3774#[gpui::test]
 3775fn test_select_all(cx: &mut TestAppContext) {
 3776    init_test(cx, |_| {});
 3777
 3778    let view = cx.add_window(|cx| {
 3779        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 3780        build_editor(buffer, cx)
 3781    });
 3782    _ = view.update(cx, |view, cx| {
 3783        view.select_all(&SelectAll, cx);
 3784        assert_eq!(
 3785            view.selections.display_ranges(cx),
 3786            &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
 3787        );
 3788    });
 3789}
 3790
 3791#[gpui::test]
 3792fn test_select_line(cx: &mut TestAppContext) {
 3793    init_test(cx, |_| {});
 3794
 3795    let view = cx.add_window(|cx| {
 3796        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 3797        build_editor(buffer, cx)
 3798    });
 3799    _ = view.update(cx, |view, cx| {
 3800        view.change_selections(None, cx, |s| {
 3801            s.select_display_ranges([
 3802                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3803                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3804                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3805                DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
 3806            ])
 3807        });
 3808        view.select_line(&SelectLine, cx);
 3809        assert_eq!(
 3810            view.selections.display_ranges(cx),
 3811            vec![
 3812                DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
 3813                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
 3814            ]
 3815        );
 3816    });
 3817
 3818    _ = view.update(cx, |view, cx| {
 3819        view.select_line(&SelectLine, cx);
 3820        assert_eq!(
 3821            view.selections.display_ranges(cx),
 3822            vec![
 3823                DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
 3824                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
 3825            ]
 3826        );
 3827    });
 3828
 3829    _ = view.update(cx, |view, cx| {
 3830        view.select_line(&SelectLine, cx);
 3831        assert_eq!(
 3832            view.selections.display_ranges(cx),
 3833            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
 3834        );
 3835    });
 3836}
 3837
 3838#[gpui::test]
 3839fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 3840    init_test(cx, |_| {});
 3841
 3842    let view = cx.add_window(|cx| {
 3843        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 3844        build_editor(buffer, cx)
 3845    });
 3846    _ = view.update(cx, |view, cx| {
 3847        view.fold_ranges(
 3848            vec![
 3849                Point::new(0, 2)..Point::new(1, 2),
 3850                Point::new(2, 3)..Point::new(4, 1),
 3851                Point::new(7, 0)..Point::new(8, 4),
 3852            ],
 3853            true,
 3854            cx,
 3855        );
 3856        view.change_selections(None, cx, |s| {
 3857            s.select_display_ranges([
 3858                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3859                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3860                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3861                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
 3862            ])
 3863        });
 3864        assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
 3865    });
 3866
 3867    _ = view.update(cx, |view, cx| {
 3868        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
 3869        assert_eq!(
 3870            view.display_text(cx),
 3871            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 3872        );
 3873        assert_eq!(
 3874            view.selections.display_ranges(cx),
 3875            [
 3876                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 3877                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3878                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
 3879                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
 3880            ]
 3881        );
 3882    });
 3883
 3884    _ = view.update(cx, |view, cx| {
 3885        view.change_selections(None, cx, |s| {
 3886            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
 3887        });
 3888        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
 3889        assert_eq!(
 3890            view.display_text(cx),
 3891            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 3892        );
 3893        assert_eq!(
 3894            view.selections.display_ranges(cx),
 3895            [
 3896                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
 3897                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
 3898                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
 3899                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
 3900                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
 3901                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
 3902                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
 3903                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
 3904            ]
 3905        );
 3906    });
 3907}
 3908
 3909#[gpui::test]
 3910async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 3911    init_test(cx, |_| {});
 3912
 3913    let mut cx = EditorTestContext::new(cx).await;
 3914
 3915    // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
 3916    cx.set_state(indoc!(
 3917        r#"abc
 3918           defˇghi
 3919
 3920           jk
 3921           nlmo
 3922           "#
 3923    ));
 3924
 3925    cx.update_editor(|editor, cx| {
 3926        editor.add_selection_above(&Default::default(), cx);
 3927    });
 3928
 3929    cx.assert_editor_state(indoc!(
 3930        r#"abcˇ
 3931           defˇghi
 3932
 3933           jk
 3934           nlmo
 3935           "#
 3936    ));
 3937
 3938    cx.update_editor(|editor, cx| {
 3939        editor.add_selection_above(&Default::default(), cx);
 3940    });
 3941
 3942    cx.assert_editor_state(indoc!(
 3943        r#"abcˇ
 3944            defˇghi
 3945
 3946            jk
 3947            nlmo
 3948            "#
 3949    ));
 3950
 3951    cx.update_editor(|view, cx| {
 3952        view.add_selection_below(&Default::default(), cx);
 3953    });
 3954
 3955    cx.assert_editor_state(indoc!(
 3956        r#"abc
 3957           defˇghi
 3958
 3959           jk
 3960           nlmo
 3961           "#
 3962    ));
 3963
 3964    cx.update_editor(|view, cx| {
 3965        view.undo_selection(&Default::default(), cx);
 3966    });
 3967
 3968    cx.assert_editor_state(indoc!(
 3969        r#"abcˇ
 3970           defˇghi
 3971
 3972           jk
 3973           nlmo
 3974           "#
 3975    ));
 3976
 3977    cx.update_editor(|view, cx| {
 3978        view.redo_selection(&Default::default(), cx);
 3979    });
 3980
 3981    cx.assert_editor_state(indoc!(
 3982        r#"abc
 3983           defˇghi
 3984
 3985           jk
 3986           nlmo
 3987           "#
 3988    ));
 3989
 3990    cx.update_editor(|view, cx| {
 3991        view.add_selection_below(&Default::default(), cx);
 3992    });
 3993
 3994    cx.assert_editor_state(indoc!(
 3995        r#"abc
 3996           defˇghi
 3997
 3998           jk
 3999           nlmˇo
 4000           "#
 4001    ));
 4002
 4003    cx.update_editor(|view, cx| {
 4004        view.add_selection_below(&Default::default(), cx);
 4005    });
 4006
 4007    cx.assert_editor_state(indoc!(
 4008        r#"abc
 4009           defˇghi
 4010
 4011           jk
 4012           nlmˇo
 4013           "#
 4014    ));
 4015
 4016    // change selections
 4017    cx.set_state(indoc!(
 4018        r#"abc
 4019           def«ˇg»hi
 4020
 4021           jk
 4022           nlmo
 4023           "#
 4024    ));
 4025
 4026    cx.update_editor(|view, cx| {
 4027        view.add_selection_below(&Default::default(), cx);
 4028    });
 4029
 4030    cx.assert_editor_state(indoc!(
 4031        r#"abc
 4032           def«ˇg»hi
 4033
 4034           jk
 4035           nlm«ˇo»
 4036           "#
 4037    ));
 4038
 4039    cx.update_editor(|view, cx| {
 4040        view.add_selection_below(&Default::default(), cx);
 4041    });
 4042
 4043    cx.assert_editor_state(indoc!(
 4044        r#"abc
 4045           def«ˇg»hi
 4046
 4047           jk
 4048           nlm«ˇo»
 4049           "#
 4050    ));
 4051
 4052    cx.update_editor(|view, cx| {
 4053        view.add_selection_above(&Default::default(), cx);
 4054    });
 4055
 4056    cx.assert_editor_state(indoc!(
 4057        r#"abc
 4058           def«ˇg»hi
 4059
 4060           jk
 4061           nlmo
 4062           "#
 4063    ));
 4064
 4065    cx.update_editor(|view, cx| {
 4066        view.add_selection_above(&Default::default(), cx);
 4067    });
 4068
 4069    cx.assert_editor_state(indoc!(
 4070        r#"abc
 4071           def«ˇg»hi
 4072
 4073           jk
 4074           nlmo
 4075           "#
 4076    ));
 4077
 4078    // Change selections again
 4079    cx.set_state(indoc!(
 4080        r#"a«bc
 4081           defgˇ»hi
 4082
 4083           jk
 4084           nlmo
 4085           "#
 4086    ));
 4087
 4088    cx.update_editor(|view, cx| {
 4089        view.add_selection_below(&Default::default(), cx);
 4090    });
 4091
 4092    cx.assert_editor_state(indoc!(
 4093        r#"a«bcˇ»
 4094           d«efgˇ»hi
 4095
 4096           j«kˇ»
 4097           nlmo
 4098           "#
 4099    ));
 4100
 4101    cx.update_editor(|view, cx| {
 4102        view.add_selection_below(&Default::default(), cx);
 4103    });
 4104    cx.assert_editor_state(indoc!(
 4105        r#"a«bcˇ»
 4106           d«efgˇ»hi
 4107
 4108           j«kˇ»
 4109           n«lmoˇ»
 4110           "#
 4111    ));
 4112    cx.update_editor(|view, cx| {
 4113        view.add_selection_above(&Default::default(), cx);
 4114    });
 4115
 4116    cx.assert_editor_state(indoc!(
 4117        r#"a«bcˇ»
 4118           d«efgˇ»hi
 4119
 4120           j«kˇ»
 4121           nlmo
 4122           "#
 4123    ));
 4124
 4125    // Change selections again
 4126    cx.set_state(indoc!(
 4127        r#"abc
 4128           d«ˇefghi
 4129
 4130           jk
 4131           nlm»o
 4132           "#
 4133    ));
 4134
 4135    cx.update_editor(|view, cx| {
 4136        view.add_selection_above(&Default::default(), cx);
 4137    });
 4138
 4139    cx.assert_editor_state(indoc!(
 4140        r#"a«ˇbc»
 4141           d«ˇef»ghi
 4142
 4143           j«ˇk»
 4144           n«ˇlm»o
 4145           "#
 4146    ));
 4147
 4148    cx.update_editor(|view, cx| {
 4149        view.add_selection_below(&Default::default(), cx);
 4150    });
 4151
 4152    cx.assert_editor_state(indoc!(
 4153        r#"abc
 4154           d«ˇef»ghi
 4155
 4156           j«ˇk»
 4157           n«ˇlm»o
 4158           "#
 4159    ));
 4160}
 4161
 4162#[gpui::test]
 4163async fn test_select_next(cx: &mut gpui::TestAppContext) {
 4164    init_test(cx, |_| {});
 4165
 4166    let mut cx = EditorTestContext::new(cx).await;
 4167    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4168
 4169    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4170        .unwrap();
 4171    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4172
 4173    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4174        .unwrap();
 4175    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 4176
 4177    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4178    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4179
 4180    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4181    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 4182
 4183    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4184        .unwrap();
 4185    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4186
 4187    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4188        .unwrap();
 4189    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4190}
 4191
 4192#[gpui::test]
 4193async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
 4194    init_test(cx, |_| {});
 4195
 4196    let mut cx = EditorTestContext::new(cx).await;
 4197    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4198
 4199    cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
 4200        .unwrap();
 4201    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4202}
 4203
 4204#[gpui::test]
 4205async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
 4206    init_test(cx, |_| {});
 4207
 4208    let mut cx = EditorTestContext::new(cx).await;
 4209    cx.set_state(
 4210        r#"let foo = 2;
 4211lˇet foo = 2;
 4212let fooˇ = 2;
 4213let foo = 2;
 4214let foo = ˇ2;"#,
 4215    );
 4216
 4217    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4218        .unwrap();
 4219    cx.assert_editor_state(
 4220        r#"let foo = 2;
 4221«letˇ» foo = 2;
 4222let «fooˇ» = 2;
 4223let foo = 2;
 4224let foo = «2ˇ»;"#,
 4225    );
 4226
 4227    // noop for multiple selections with different contents
 4228    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4229        .unwrap();
 4230    cx.assert_editor_state(
 4231        r#"let foo = 2;
 4232«letˇ» foo = 2;
 4233let «fooˇ» = 2;
 4234let foo = 2;
 4235let foo = «2ˇ»;"#,
 4236    );
 4237}
 4238
 4239#[gpui::test]
 4240async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
 4241    init_test(cx, |_| {});
 4242
 4243    let mut cx = EditorTestContext::new_multibuffer(
 4244        cx,
 4245        [
 4246            indoc! {
 4247                "aaa\n«bbb\nccc\n»ddd"
 4248            },
 4249            indoc! {
 4250                "aaa\n«bbb\nccc\n»ddd"
 4251            },
 4252        ],
 4253    );
 4254
 4255    cx.assert_editor_state(indoc! {"
 4256        ˇbbb
 4257        ccc
 4258
 4259        bbb
 4260        ccc
 4261        "});
 4262    cx.dispatch_action(SelectPrevious::default());
 4263    cx.assert_editor_state(indoc! {"
 4264                «bbbˇ»
 4265                ccc
 4266
 4267                bbb
 4268                ccc
 4269                "});
 4270    cx.dispatch_action(SelectPrevious::default());
 4271    cx.assert_editor_state(indoc! {"
 4272                «bbbˇ»
 4273                ccc
 4274
 4275                «bbbˇ»
 4276                ccc
 4277                "});
 4278}
 4279
 4280#[gpui::test]
 4281async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
 4282    init_test(cx, |_| {});
 4283
 4284    let mut cx = EditorTestContext::new(cx).await;
 4285    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4286
 4287    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4288        .unwrap();
 4289    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4290
 4291    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4292        .unwrap();
 4293    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 4294
 4295    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4296    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4297
 4298    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4299    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 4300
 4301    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4302        .unwrap();
 4303    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 4304
 4305    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4306        .unwrap();
 4307    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 4308
 4309    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4310        .unwrap();
 4311    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 4312}
 4313
 4314#[gpui::test]
 4315async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
 4316    init_test(cx, |_| {});
 4317
 4318    let mut cx = EditorTestContext::new(cx).await;
 4319    cx.set_state(
 4320        r#"let foo = 2;
 4321lˇet foo = 2;
 4322let fooˇ = 2;
 4323let foo = 2;
 4324let foo = ˇ2;"#,
 4325    );
 4326
 4327    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4328        .unwrap();
 4329    cx.assert_editor_state(
 4330        r#"let foo = 2;
 4331«letˇ» foo = 2;
 4332let «fooˇ» = 2;
 4333let foo = 2;
 4334let foo = «2ˇ»;"#,
 4335    );
 4336
 4337    // noop for multiple selections with different contents
 4338    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4339        .unwrap();
 4340    cx.assert_editor_state(
 4341        r#"let foo = 2;
 4342«letˇ» foo = 2;
 4343let «fooˇ» = 2;
 4344let foo = 2;
 4345let foo = «2ˇ»;"#,
 4346    );
 4347}
 4348
 4349#[gpui::test]
 4350async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
 4351    init_test(cx, |_| {});
 4352
 4353    let mut cx = EditorTestContext::new(cx).await;
 4354    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 4355
 4356    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4357        .unwrap();
 4358    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 4359
 4360    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4361        .unwrap();
 4362    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 4363
 4364    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4365    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 4366
 4367    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4368    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 4369
 4370    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4371        .unwrap();
 4372    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 4373
 4374    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4375        .unwrap();
 4376    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 4377}
 4378
 4379#[gpui::test]
 4380async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
 4381    init_test(cx, |_| {});
 4382
 4383    let language = Arc::new(Language::new(
 4384        LanguageConfig::default(),
 4385        Some(tree_sitter_rust::language()),
 4386    ));
 4387
 4388    let text = r#"
 4389        use mod1::mod2::{mod3, mod4};
 4390
 4391        fn fn_1(param1: bool, param2: &str) {
 4392            let var1 = "text";
 4393        }
 4394    "#
 4395    .unindent();
 4396
 4397    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 4398    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 4399    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 4400
 4401    view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 4402        .await;
 4403
 4404    _ = view.update(cx, |view, cx| {
 4405        view.change_selections(None, cx, |s| {
 4406            s.select_display_ranges([
 4407                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
 4408                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
 4409                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
 4410            ]);
 4411        });
 4412        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4413    });
 4414    assert_eq!(
 4415        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
 4416        &[
 4417            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
 4418            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
 4419            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
 4420        ]
 4421    );
 4422
 4423    _ = view.update(cx, |view, cx| {
 4424        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4425    });
 4426    assert_eq!(
 4427        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4428        &[
 4429            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
 4430            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
 4431        ]
 4432    );
 4433
 4434    _ = view.update(cx, |view, cx| {
 4435        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4436    });
 4437    assert_eq!(
 4438        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4439        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
 4440    );
 4441
 4442    // Trying to expand the selected syntax node one more time has no effect.
 4443    _ = view.update(cx, |view, cx| {
 4444        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4445    });
 4446    assert_eq!(
 4447        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4448        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
 4449    );
 4450
 4451    _ = view.update(cx, |view, cx| {
 4452        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4453    });
 4454    assert_eq!(
 4455        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4456        &[
 4457            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
 4458            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
 4459        ]
 4460    );
 4461
 4462    _ = view.update(cx, |view, cx| {
 4463        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4464    });
 4465    assert_eq!(
 4466        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4467        &[
 4468            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
 4469            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
 4470            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
 4471        ]
 4472    );
 4473
 4474    _ = view.update(cx, |view, cx| {
 4475        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4476    });
 4477    assert_eq!(
 4478        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4479        &[
 4480            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
 4481            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
 4482            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
 4483        ]
 4484    );
 4485
 4486    // Trying to shrink the selected syntax node one more time has no effect.
 4487    _ = view.update(cx, |view, cx| {
 4488        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4489    });
 4490    assert_eq!(
 4491        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4492        &[
 4493            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
 4494            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
 4495            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
 4496        ]
 4497    );
 4498
 4499    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 4500    // a fold.
 4501    _ = view.update(cx, |view, cx| {
 4502        view.fold_ranges(
 4503            vec![
 4504                Point::new(0, 21)..Point::new(0, 24),
 4505                Point::new(3, 20)..Point::new(3, 22),
 4506            ],
 4507            true,
 4508            cx,
 4509        );
 4510        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4511    });
 4512    assert_eq!(
 4513        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4514        &[
 4515            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
 4516            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
 4517            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
 4518        ]
 4519    );
 4520}
 4521
 4522#[gpui::test]
 4523async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 4524    init_test(cx, |_| {});
 4525
 4526    let language = Arc::new(
 4527        Language::new(
 4528            LanguageConfig {
 4529                brackets: BracketPairConfig {
 4530                    pairs: vec![
 4531                        BracketPair {
 4532                            start: "{".to_string(),
 4533                            end: "}".to_string(),
 4534                            close: false,
 4535                            newline: true,
 4536                        },
 4537                        BracketPair {
 4538                            start: "(".to_string(),
 4539                            end: ")".to_string(),
 4540                            close: false,
 4541                            newline: true,
 4542                        },
 4543                    ],
 4544                    ..Default::default()
 4545                },
 4546                ..Default::default()
 4547            },
 4548            Some(tree_sitter_rust::language()),
 4549        )
 4550        .with_indents_query(
 4551            r#"
 4552                (_ "(" ")" @end) @indent
 4553                (_ "{" "}" @end) @indent
 4554            "#,
 4555        )
 4556        .unwrap(),
 4557    );
 4558
 4559    let text = "fn a() {}";
 4560
 4561    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 4562    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 4563    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 4564    editor
 4565        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 4566        .await;
 4567
 4568    _ = editor.update(cx, |editor, cx| {
 4569        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 4570        editor.newline(&Newline, cx);
 4571        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 4572        assert_eq!(
 4573            editor.selections.ranges(cx),
 4574            &[
 4575                Point::new(1, 4)..Point::new(1, 4),
 4576                Point::new(3, 4)..Point::new(3, 4),
 4577                Point::new(5, 0)..Point::new(5, 0)
 4578            ]
 4579        );
 4580    });
 4581}
 4582
 4583#[gpui::test]
 4584async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
 4585    init_test(cx, |_| {});
 4586
 4587    let mut cx = EditorTestContext::new(cx).await;
 4588
 4589    let language = Arc::new(Language::new(
 4590        LanguageConfig {
 4591            brackets: BracketPairConfig {
 4592                pairs: vec![
 4593                    BracketPair {
 4594                        start: "{".to_string(),
 4595                        end: "}".to_string(),
 4596                        close: true,
 4597                        newline: true,
 4598                    },
 4599                    BracketPair {
 4600                        start: "(".to_string(),
 4601                        end: ")".to_string(),
 4602                        close: true,
 4603                        newline: true,
 4604                    },
 4605                    BracketPair {
 4606                        start: "/*".to_string(),
 4607                        end: " */".to_string(),
 4608                        close: true,
 4609                        newline: true,
 4610                    },
 4611                    BracketPair {
 4612                        start: "[".to_string(),
 4613                        end: "]".to_string(),
 4614                        close: false,
 4615                        newline: true,
 4616                    },
 4617                    BracketPair {
 4618                        start: "\"".to_string(),
 4619                        end: "\"".to_string(),
 4620                        close: true,
 4621                        newline: false,
 4622                    },
 4623                ],
 4624                ..Default::default()
 4625            },
 4626            autoclose_before: "})]".to_string(),
 4627            ..Default::default()
 4628        },
 4629        Some(tree_sitter_rust::language()),
 4630    ));
 4631
 4632    cx.language_registry().add(language.clone());
 4633    cx.update_buffer(|buffer, cx| {
 4634        buffer.set_language(Some(language), cx);
 4635    });
 4636
 4637    cx.set_state(
 4638        &r#"
 4639            🏀ˇ
 4640            εˇ
 4641            ❤️ˇ
 4642        "#
 4643        .unindent(),
 4644    );
 4645
 4646    // autoclose multiple nested brackets at multiple cursors
 4647    cx.update_editor(|view, cx| {
 4648        view.handle_input("{", cx);
 4649        view.handle_input("{", cx);
 4650        view.handle_input("{", cx);
 4651    });
 4652    cx.assert_editor_state(
 4653        &"
 4654            🏀{{{ˇ}}}
 4655            ε{{{ˇ}}}
 4656            ❤️{{{ˇ}}}
 4657        "
 4658        .unindent(),
 4659    );
 4660
 4661    // insert a different closing bracket
 4662    cx.update_editor(|view, cx| {
 4663        view.handle_input(")", cx);
 4664    });
 4665    cx.assert_editor_state(
 4666        &"
 4667            🏀{{{)ˇ}}}
 4668            ε{{{)ˇ}}}
 4669            ❤️{{{)ˇ}}}
 4670        "
 4671        .unindent(),
 4672    );
 4673
 4674    // skip over the auto-closed brackets when typing a closing bracket
 4675    cx.update_editor(|view, cx| {
 4676        view.move_right(&MoveRight, cx);
 4677        view.handle_input("}", cx);
 4678        view.handle_input("}", cx);
 4679        view.handle_input("}", cx);
 4680    });
 4681    cx.assert_editor_state(
 4682        &"
 4683            🏀{{{)}}}}ˇ
 4684            ε{{{)}}}}ˇ
 4685            ❤️{{{)}}}}ˇ
 4686        "
 4687        .unindent(),
 4688    );
 4689
 4690    // autoclose multi-character pairs
 4691    cx.set_state(
 4692        &"
 4693            ˇ
 4694            ˇ
 4695        "
 4696        .unindent(),
 4697    );
 4698    cx.update_editor(|view, cx| {
 4699        view.handle_input("/", cx);
 4700        view.handle_input("*", cx);
 4701    });
 4702    cx.assert_editor_state(
 4703        &"
 4704            /*ˇ */
 4705            /*ˇ */
 4706        "
 4707        .unindent(),
 4708    );
 4709
 4710    // one cursor autocloses a multi-character pair, one cursor
 4711    // does not autoclose.
 4712    cx.set_state(
 4713        &"
 4714 4715            ˇ
 4716        "
 4717        .unindent(),
 4718    );
 4719    cx.update_editor(|view, cx| view.handle_input("*", cx));
 4720    cx.assert_editor_state(
 4721        &"
 4722            /*ˇ */
 4723 4724        "
 4725        .unindent(),
 4726    );
 4727
 4728    // Don't autoclose if the next character isn't whitespace and isn't
 4729    // listed in the language's "autoclose_before" section.
 4730    cx.set_state("ˇa b");
 4731    cx.update_editor(|view, cx| view.handle_input("{", cx));
 4732    cx.assert_editor_state("{ˇa b");
 4733
 4734    // Don't autoclose if `close` is false for the bracket pair
 4735    cx.set_state("ˇ");
 4736    cx.update_editor(|view, cx| view.handle_input("[", cx));
 4737    cx.assert_editor_state("");
 4738
 4739    // Surround with brackets if text is selected
 4740    cx.set_state("«aˇ» b");
 4741    cx.update_editor(|view, cx| view.handle_input("{", cx));
 4742    cx.assert_editor_state("{«aˇ»} b");
 4743
 4744    // Autclose pair where the start and end characters are the same
 4745    cx.set_state("");
 4746    cx.update_editor(|view, cx| view.handle_input("\"", cx));
 4747    cx.assert_editor_state("a\"ˇ\"");
 4748    cx.update_editor(|view, cx| view.handle_input("\"", cx));
 4749    cx.assert_editor_state("a\"\"ˇ");
 4750}
 4751
 4752#[gpui::test]
 4753async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
 4754    init_test(cx, |settings| {
 4755        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 4756    });
 4757
 4758    let mut cx = EditorTestContext::new(cx).await;
 4759
 4760    let language = Arc::new(Language::new(
 4761        LanguageConfig {
 4762            brackets: BracketPairConfig {
 4763                pairs: vec![
 4764                    BracketPair {
 4765                        start: "{".to_string(),
 4766                        end: "}".to_string(),
 4767                        close: true,
 4768                        newline: true,
 4769                    },
 4770                    BracketPair {
 4771                        start: "(".to_string(),
 4772                        end: ")".to_string(),
 4773                        close: true,
 4774                        newline: true,
 4775                    },
 4776                    BracketPair {
 4777                        start: "[".to_string(),
 4778                        end: "]".to_string(),
 4779                        close: false,
 4780                        newline: true,
 4781                    },
 4782                ],
 4783                ..Default::default()
 4784            },
 4785            autoclose_before: "})]".to_string(),
 4786            ..Default::default()
 4787        },
 4788        Some(tree_sitter_rust::language()),
 4789    ));
 4790
 4791    cx.language_registry().add(language.clone());
 4792    cx.update_buffer(|buffer, cx| {
 4793        buffer.set_language(Some(language), cx);
 4794    });
 4795
 4796    cx.set_state(
 4797        &"
 4798            ˇ
 4799            ˇ
 4800            ˇ
 4801        "
 4802        .unindent(),
 4803    );
 4804
 4805    // ensure only matching closing brackets are skipped over
 4806    cx.update_editor(|view, cx| {
 4807        view.handle_input("}", cx);
 4808        view.move_left(&MoveLeft, cx);
 4809        view.handle_input(")", cx);
 4810        view.move_left(&MoveLeft, cx);
 4811    });
 4812    cx.assert_editor_state(
 4813        &"
 4814            ˇ)}
 4815            ˇ)}
 4816            ˇ)}
 4817        "
 4818        .unindent(),
 4819    );
 4820
 4821    // skip-over closing brackets at multiple cursors
 4822    cx.update_editor(|view, cx| {
 4823        view.handle_input(")", cx);
 4824        view.handle_input("}", cx);
 4825    });
 4826    cx.assert_editor_state(
 4827        &"
 4828            )}ˇ
 4829            )}ˇ
 4830            )}ˇ
 4831        "
 4832        .unindent(),
 4833    );
 4834
 4835    // ignore non-close brackets
 4836    cx.update_editor(|view, cx| {
 4837        view.handle_input("]", cx);
 4838        view.move_left(&MoveLeft, cx);
 4839        view.handle_input("]", cx);
 4840    });
 4841    cx.assert_editor_state(
 4842        &"
 4843            )}]ˇ]
 4844            )}]ˇ]
 4845            )}]ˇ]
 4846        "
 4847        .unindent(),
 4848    );
 4849}
 4850
 4851#[gpui::test]
 4852async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
 4853    init_test(cx, |_| {});
 4854
 4855    let mut cx = EditorTestContext::new(cx).await;
 4856
 4857    let html_language = Arc::new(
 4858        Language::new(
 4859            LanguageConfig {
 4860                name: "HTML".into(),
 4861                brackets: BracketPairConfig {
 4862                    pairs: vec![
 4863                        BracketPair {
 4864                            start: "<".into(),
 4865                            end: ">".into(),
 4866                            close: true,
 4867                            ..Default::default()
 4868                        },
 4869                        BracketPair {
 4870                            start: "{".into(),
 4871                            end: "}".into(),
 4872                            close: true,
 4873                            ..Default::default()
 4874                        },
 4875                        BracketPair {
 4876                            start: "(".into(),
 4877                            end: ")".into(),
 4878                            close: true,
 4879                            ..Default::default()
 4880                        },
 4881                    ],
 4882                    ..Default::default()
 4883                },
 4884                autoclose_before: "})]>".into(),
 4885                ..Default::default()
 4886            },
 4887            Some(tree_sitter_html::language()),
 4888        )
 4889        .with_injection_query(
 4890            r#"
 4891            (script_element
 4892                (raw_text) @content
 4893                (#set! "language" "javascript"))
 4894            "#,
 4895        )
 4896        .unwrap(),
 4897    );
 4898
 4899    let javascript_language = Arc::new(Language::new(
 4900        LanguageConfig {
 4901            name: "JavaScript".into(),
 4902            brackets: BracketPairConfig {
 4903                pairs: vec![
 4904                    BracketPair {
 4905                        start: "/*".into(),
 4906                        end: " */".into(),
 4907                        close: true,
 4908                        ..Default::default()
 4909                    },
 4910                    BracketPair {
 4911                        start: "{".into(),
 4912                        end: "}".into(),
 4913                        close: true,
 4914                        ..Default::default()
 4915                    },
 4916                    BracketPair {
 4917                        start: "(".into(),
 4918                        end: ")".into(),
 4919                        close: true,
 4920                        ..Default::default()
 4921                    },
 4922                ],
 4923                ..Default::default()
 4924            },
 4925            autoclose_before: "})]>".into(),
 4926            ..Default::default()
 4927        },
 4928        Some(tree_sitter_typescript::language_tsx()),
 4929    ));
 4930
 4931    cx.language_registry().add(html_language.clone());
 4932    cx.language_registry().add(javascript_language.clone());
 4933
 4934    cx.update_buffer(|buffer, cx| {
 4935        buffer.set_language(Some(html_language), cx);
 4936    });
 4937
 4938    cx.set_state(
 4939        &r#"
 4940            <body>ˇ
 4941                <script>
 4942                    var x = 1;ˇ
 4943                </script>
 4944            </body>ˇ
 4945        "#
 4946        .unindent(),
 4947    );
 4948
 4949    // Precondition: different languages are active at different locations.
 4950    cx.update_editor(|editor, cx| {
 4951        let snapshot = editor.snapshot(cx);
 4952        let cursors = editor.selections.ranges::<usize>(cx);
 4953        let languages = cursors
 4954            .iter()
 4955            .map(|c| snapshot.language_at(c.start).unwrap().name())
 4956            .collect::<Vec<_>>();
 4957        assert_eq!(
 4958            languages,
 4959            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 4960        );
 4961    });
 4962
 4963    // Angle brackets autoclose in HTML, but not JavaScript.
 4964    cx.update_editor(|editor, cx| {
 4965        editor.handle_input("<", cx);
 4966        editor.handle_input("a", cx);
 4967    });
 4968    cx.assert_editor_state(
 4969        &r#"
 4970            <body><aˇ>
 4971                <script>
 4972                    var x = 1;<aˇ
 4973                </script>
 4974            </body><aˇ>
 4975        "#
 4976        .unindent(),
 4977    );
 4978
 4979    // Curly braces and parens autoclose in both HTML and JavaScript.
 4980    cx.update_editor(|editor, cx| {
 4981        editor.handle_input(" b=", cx);
 4982        editor.handle_input("{", cx);
 4983        editor.handle_input("c", cx);
 4984        editor.handle_input("(", cx);
 4985    });
 4986    cx.assert_editor_state(
 4987        &r#"
 4988            <body><a b={c(ˇ)}>
 4989                <script>
 4990                    var x = 1;<a b={c(ˇ)}
 4991                </script>
 4992            </body><a b={c(ˇ)}>
 4993        "#
 4994        .unindent(),
 4995    );
 4996
 4997    // Brackets that were already autoclosed are skipped.
 4998    cx.update_editor(|editor, cx| {
 4999        editor.handle_input(")", cx);
 5000        editor.handle_input("d", cx);
 5001        editor.handle_input("}", cx);
 5002    });
 5003    cx.assert_editor_state(
 5004        &r#"
 5005            <body><a b={c()d}ˇ>
 5006                <script>
 5007                    var x = 1;<a b={c()d}ˇ
 5008                </script>
 5009            </body><a b={c()d}ˇ>
 5010        "#
 5011        .unindent(),
 5012    );
 5013    cx.update_editor(|editor, cx| {
 5014        editor.handle_input(">", cx);
 5015    });
 5016    cx.assert_editor_state(
 5017        &r#"
 5018            <body><a b={c()d}>ˇ
 5019                <script>
 5020                    var x = 1;<a b={c()d}>ˇ
 5021                </script>
 5022            </body><a b={c()d}>ˇ
 5023        "#
 5024        .unindent(),
 5025    );
 5026
 5027    // Reset
 5028    cx.set_state(
 5029        &r#"
 5030            <body>ˇ
 5031                <script>
 5032                    var x = 1;ˇ
 5033                </script>
 5034            </body>ˇ
 5035        "#
 5036        .unindent(),
 5037    );
 5038
 5039    cx.update_editor(|editor, cx| {
 5040        editor.handle_input("<", cx);
 5041    });
 5042    cx.assert_editor_state(
 5043        &r#"
 5044            <body><ˇ>
 5045                <script>
 5046                    var x = 1;<ˇ
 5047                </script>
 5048            </body><ˇ>
 5049        "#
 5050        .unindent(),
 5051    );
 5052
 5053    // When backspacing, the closing angle brackets are removed.
 5054    cx.update_editor(|editor, cx| {
 5055        editor.backspace(&Backspace, cx);
 5056    });
 5057    cx.assert_editor_state(
 5058        &r#"
 5059            <body>ˇ
 5060                <script>
 5061                    var x = 1;ˇ
 5062                </script>
 5063            </body>ˇ
 5064        "#
 5065        .unindent(),
 5066    );
 5067
 5068    // Block comments autoclose in JavaScript, but not HTML.
 5069    cx.update_editor(|editor, cx| {
 5070        editor.handle_input("/", cx);
 5071        editor.handle_input("*", cx);
 5072    });
 5073    cx.assert_editor_state(
 5074        &r#"
 5075            <body>/*ˇ
 5076                <script>
 5077                    var x = 1;/*ˇ */
 5078                </script>
 5079            </body>/*ˇ
 5080        "#
 5081        .unindent(),
 5082    );
 5083}
 5084
 5085#[gpui::test]
 5086async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
 5087    init_test(cx, |_| {});
 5088
 5089    let mut cx = EditorTestContext::new(cx).await;
 5090
 5091    let rust_language = Arc::new(
 5092        Language::new(
 5093            LanguageConfig {
 5094                name: "Rust".into(),
 5095                brackets: serde_json::from_value(json!([
 5096                    { "start": "{", "end": "}", "close": true, "newline": true },
 5097                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 5098                ]))
 5099                .unwrap(),
 5100                autoclose_before: "})]>".into(),
 5101                ..Default::default()
 5102            },
 5103            Some(tree_sitter_rust::language()),
 5104        )
 5105        .with_override_query("(string_literal) @string")
 5106        .unwrap(),
 5107    );
 5108
 5109    cx.language_registry().add(rust_language.clone());
 5110    cx.update_buffer(|buffer, cx| {
 5111        buffer.set_language(Some(rust_language), cx);
 5112    });
 5113
 5114    cx.set_state(
 5115        &r#"
 5116            let x = ˇ
 5117        "#
 5118        .unindent(),
 5119    );
 5120
 5121    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 5122    cx.update_editor(|editor, cx| {
 5123        editor.handle_input("\"", cx);
 5124    });
 5125    cx.assert_editor_state(
 5126        &r#"
 5127            let x = "ˇ"
 5128        "#
 5129        .unindent(),
 5130    );
 5131
 5132    // Inserting another quotation mark. The cursor moves across the existing
 5133    // automatically-inserted quotation mark.
 5134    cx.update_editor(|editor, cx| {
 5135        editor.handle_input("\"", cx);
 5136    });
 5137    cx.assert_editor_state(
 5138        &r#"
 5139            let x = ""ˇ
 5140        "#
 5141        .unindent(),
 5142    );
 5143
 5144    // Reset
 5145    cx.set_state(
 5146        &r#"
 5147            let x = ˇ
 5148        "#
 5149        .unindent(),
 5150    );
 5151
 5152    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 5153    cx.update_editor(|editor, cx| {
 5154        editor.handle_input("\"", cx);
 5155        editor.handle_input(" ", cx);
 5156        editor.move_left(&Default::default(), cx);
 5157        editor.handle_input("\\", cx);
 5158        editor.handle_input("\"", cx);
 5159    });
 5160    cx.assert_editor_state(
 5161        &r#"
 5162            let x = "\"ˇ "
 5163        "#
 5164        .unindent(),
 5165    );
 5166
 5167    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 5168    // mark. Nothing is inserted.
 5169    cx.update_editor(|editor, cx| {
 5170        editor.move_right(&Default::default(), cx);
 5171        editor.handle_input("\"", cx);
 5172    });
 5173    cx.assert_editor_state(
 5174        &r#"
 5175            let x = "\" "ˇ
 5176        "#
 5177        .unindent(),
 5178    );
 5179}
 5180
 5181#[gpui::test]
 5182async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
 5183    init_test(cx, |_| {});
 5184
 5185    let language = Arc::new(Language::new(
 5186        LanguageConfig {
 5187            brackets: BracketPairConfig {
 5188                pairs: vec![
 5189                    BracketPair {
 5190                        start: "{".to_string(),
 5191                        end: "}".to_string(),
 5192                        close: true,
 5193                        newline: true,
 5194                    },
 5195                    BracketPair {
 5196                        start: "/* ".to_string(),
 5197                        end: "*/".to_string(),
 5198                        close: true,
 5199                        ..Default::default()
 5200                    },
 5201                ],
 5202                ..Default::default()
 5203            },
 5204            ..Default::default()
 5205        },
 5206        Some(tree_sitter_rust::language()),
 5207    ));
 5208
 5209    let text = r#"
 5210        a
 5211        b
 5212        c
 5213    "#
 5214    .unindent();
 5215
 5216    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 5217    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5218    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5219    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5220        .await;
 5221
 5222    _ = view.update(cx, |view, cx| {
 5223        view.change_selections(None, cx, |s| {
 5224            s.select_display_ranges([
 5225                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 5226                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 5227                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
 5228            ])
 5229        });
 5230
 5231        view.handle_input("{", cx);
 5232        view.handle_input("{", cx);
 5233        view.handle_input("{", cx);
 5234        assert_eq!(
 5235            view.text(cx),
 5236            "
 5237                {{{a}}}
 5238                {{{b}}}
 5239                {{{c}}}
 5240            "
 5241            .unindent()
 5242        );
 5243        assert_eq!(
 5244            view.selections.display_ranges(cx),
 5245            [
 5246                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
 5247                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
 5248                DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
 5249            ]
 5250        );
 5251
 5252        view.undo(&Undo, cx);
 5253        view.undo(&Undo, cx);
 5254        view.undo(&Undo, cx);
 5255        assert_eq!(
 5256            view.text(cx),
 5257            "
 5258                a
 5259                b
 5260                c
 5261            "
 5262            .unindent()
 5263        );
 5264        assert_eq!(
 5265            view.selections.display_ranges(cx),
 5266            [
 5267                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 5268                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 5269                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
 5270            ]
 5271        );
 5272
 5273        // Ensure inserting the first character of a multi-byte bracket pair
 5274        // doesn't surround the selections with the bracket.
 5275        view.handle_input("/", cx);
 5276        assert_eq!(
 5277            view.text(cx),
 5278            "
 5279                /
 5280                /
 5281                /
 5282            "
 5283            .unindent()
 5284        );
 5285        assert_eq!(
 5286            view.selections.display_ranges(cx),
 5287            [
 5288                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 5289                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 5290                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
 5291            ]
 5292        );
 5293
 5294        view.undo(&Undo, cx);
 5295        assert_eq!(
 5296            view.text(cx),
 5297            "
 5298                a
 5299                b
 5300                c
 5301            "
 5302            .unindent()
 5303        );
 5304        assert_eq!(
 5305            view.selections.display_ranges(cx),
 5306            [
 5307                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 5308                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 5309                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
 5310            ]
 5311        );
 5312
 5313        // Ensure inserting the last character of a multi-byte bracket pair
 5314        // doesn't surround the selections with the bracket.
 5315        view.handle_input("*", cx);
 5316        assert_eq!(
 5317            view.text(cx),
 5318            "
 5319                *
 5320                *
 5321                *
 5322            "
 5323            .unindent()
 5324        );
 5325        assert_eq!(
 5326            view.selections.display_ranges(cx),
 5327            [
 5328                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 5329                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 5330                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
 5331            ]
 5332        );
 5333    });
 5334}
 5335
 5336#[gpui::test]
 5337async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
 5338    init_test(cx, |_| {});
 5339
 5340    let language = Arc::new(Language::new(
 5341        LanguageConfig {
 5342            brackets: BracketPairConfig {
 5343                pairs: vec![BracketPair {
 5344                    start: "{".to_string(),
 5345                    end: "}".to_string(),
 5346                    close: true,
 5347                    newline: true,
 5348                }],
 5349                ..Default::default()
 5350            },
 5351            autoclose_before: "}".to_string(),
 5352            ..Default::default()
 5353        },
 5354        Some(tree_sitter_rust::language()),
 5355    ));
 5356
 5357    let text = r#"
 5358        a
 5359        b
 5360        c
 5361    "#
 5362    .unindent();
 5363
 5364    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 5365    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5366    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5367    editor
 5368        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5369        .await;
 5370
 5371    _ = editor.update(cx, |editor, cx| {
 5372        editor.change_selections(None, cx, |s| {
 5373            s.select_ranges([
 5374                Point::new(0, 1)..Point::new(0, 1),
 5375                Point::new(1, 1)..Point::new(1, 1),
 5376                Point::new(2, 1)..Point::new(2, 1),
 5377            ])
 5378        });
 5379
 5380        editor.handle_input("{", cx);
 5381        editor.handle_input("{", cx);
 5382        editor.handle_input("_", cx);
 5383        assert_eq!(
 5384            editor.text(cx),
 5385            "
 5386                a{{_}}
 5387                b{{_}}
 5388                c{{_}}
 5389            "
 5390            .unindent()
 5391        );
 5392        assert_eq!(
 5393            editor.selections.ranges::<Point>(cx),
 5394            [
 5395                Point::new(0, 4)..Point::new(0, 4),
 5396                Point::new(1, 4)..Point::new(1, 4),
 5397                Point::new(2, 4)..Point::new(2, 4)
 5398            ]
 5399        );
 5400
 5401        editor.backspace(&Default::default(), cx);
 5402        editor.backspace(&Default::default(), cx);
 5403        assert_eq!(
 5404            editor.text(cx),
 5405            "
 5406                a{}
 5407                b{}
 5408                c{}
 5409            "
 5410            .unindent()
 5411        );
 5412        assert_eq!(
 5413            editor.selections.ranges::<Point>(cx),
 5414            [
 5415                Point::new(0, 2)..Point::new(0, 2),
 5416                Point::new(1, 2)..Point::new(1, 2),
 5417                Point::new(2, 2)..Point::new(2, 2)
 5418            ]
 5419        );
 5420
 5421        editor.delete_to_previous_word_start(&Default::default(), cx);
 5422        assert_eq!(
 5423            editor.text(cx),
 5424            "
 5425                a
 5426                b
 5427                c
 5428            "
 5429            .unindent()
 5430        );
 5431        assert_eq!(
 5432            editor.selections.ranges::<Point>(cx),
 5433            [
 5434                Point::new(0, 1)..Point::new(0, 1),
 5435                Point::new(1, 1)..Point::new(1, 1),
 5436                Point::new(2, 1)..Point::new(2, 1)
 5437            ]
 5438        );
 5439    });
 5440}
 5441
 5442#[gpui::test]
 5443async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
 5444    init_test(cx, |settings| {
 5445        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 5446    });
 5447
 5448    let mut cx = EditorTestContext::new(cx).await;
 5449
 5450    let language = Arc::new(Language::new(
 5451        LanguageConfig {
 5452            brackets: BracketPairConfig {
 5453                pairs: vec![
 5454                    BracketPair {
 5455                        start: "{".to_string(),
 5456                        end: "}".to_string(),
 5457                        close: true,
 5458                        newline: true,
 5459                    },
 5460                    BracketPair {
 5461                        start: "(".to_string(),
 5462                        end: ")".to_string(),
 5463                        close: true,
 5464                        newline: true,
 5465                    },
 5466                    BracketPair {
 5467                        start: "[".to_string(),
 5468                        end: "]".to_string(),
 5469                        close: false,
 5470                        newline: true,
 5471                    },
 5472                ],
 5473                ..Default::default()
 5474            },
 5475            autoclose_before: "})]".to_string(),
 5476            ..Default::default()
 5477        },
 5478        Some(tree_sitter_rust::language()),
 5479    ));
 5480
 5481    cx.language_registry().add(language.clone());
 5482    cx.update_buffer(|buffer, cx| {
 5483        buffer.set_language(Some(language), cx);
 5484    });
 5485
 5486    cx.set_state(
 5487        &"
 5488            {(ˇ)}
 5489            [[ˇ]]
 5490            {(ˇ)}
 5491        "
 5492        .unindent(),
 5493    );
 5494
 5495    cx.update_editor(|view, cx| {
 5496        view.backspace(&Default::default(), cx);
 5497        view.backspace(&Default::default(), cx);
 5498    });
 5499
 5500    cx.assert_editor_state(
 5501        &"
 5502            ˇ
 5503            ˇ]]
 5504            ˇ
 5505        "
 5506        .unindent(),
 5507    );
 5508
 5509    cx.update_editor(|view, cx| {
 5510        view.handle_input("{", cx);
 5511        view.handle_input("{", cx);
 5512        view.move_right(&MoveRight, cx);
 5513        view.move_right(&MoveRight, cx);
 5514        view.move_left(&MoveLeft, cx);
 5515        view.move_left(&MoveLeft, cx);
 5516        view.backspace(&Default::default(), cx);
 5517    });
 5518
 5519    cx.assert_editor_state(
 5520        &"
 5521            {ˇ}
 5522            {ˇ}]]
 5523            {ˇ}
 5524        "
 5525        .unindent(),
 5526    );
 5527
 5528    cx.update_editor(|view, cx| {
 5529        view.backspace(&Default::default(), cx);
 5530    });
 5531
 5532    cx.assert_editor_state(
 5533        &"
 5534            ˇ
 5535            ˇ]]
 5536            ˇ
 5537        "
 5538        .unindent(),
 5539    );
 5540}
 5541
 5542#[gpui::test]
 5543async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
 5544    init_test(cx, |_| {});
 5545
 5546    let language = Arc::new(Language::new(
 5547        LanguageConfig::default(),
 5548        Some(tree_sitter_rust::language()),
 5549    ));
 5550
 5551    let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
 5552    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5553    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5554    editor
 5555        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5556        .await;
 5557
 5558    _ = editor.update(cx, |editor, cx| {
 5559        editor.set_auto_replace_emoji_shortcode(true);
 5560
 5561        editor.handle_input("Hello ", cx);
 5562        editor.handle_input(":wave", cx);
 5563        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 5564
 5565        editor.handle_input(":", cx);
 5566        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 5567
 5568        editor.handle_input(" :smile", cx);
 5569        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 5570
 5571        editor.handle_input(":", cx);
 5572        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 5573
 5574        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 5575        editor.handle_input(":wave", cx);
 5576        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 5577
 5578        editor.handle_input(":", cx);
 5579        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 5580
 5581        editor.handle_input(":1", cx);
 5582        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 5583
 5584        editor.handle_input(":", cx);
 5585        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 5586
 5587        // Ensure shortcode does not get replaced when it is part of a word
 5588        editor.handle_input(" Test:wave", cx);
 5589        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 5590
 5591        editor.handle_input(":", cx);
 5592        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 5593
 5594        editor.set_auto_replace_emoji_shortcode(false);
 5595
 5596        // Ensure shortcode does not get replaced when auto replace is off
 5597        editor.handle_input(" :wave", cx);
 5598        assert_eq!(
 5599            editor.text(cx),
 5600            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 5601        );
 5602
 5603        editor.handle_input(":", cx);
 5604        assert_eq!(
 5605            editor.text(cx),
 5606            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 5607        );
 5608    });
 5609}
 5610
 5611#[gpui::test]
 5612async fn test_snippets(cx: &mut gpui::TestAppContext) {
 5613    init_test(cx, |_| {});
 5614
 5615    let (text, insertion_ranges) = marked_text_ranges(
 5616        indoc! {"
 5617            a.ˇ b
 5618            a.ˇ b
 5619            a.ˇ b
 5620        "},
 5621        false,
 5622    );
 5623
 5624    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 5625    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5626
 5627    _ = editor.update(cx, |editor, cx| {
 5628        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 5629
 5630        editor
 5631            .insert_snippet(&insertion_ranges, snippet, cx)
 5632            .unwrap();
 5633
 5634        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
 5635            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 5636            assert_eq!(editor.text(cx), expected_text);
 5637            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 5638        }
 5639
 5640        assert(
 5641            editor,
 5642            cx,
 5643            indoc! {"
 5644                a.f(«one», two, «three») b
 5645                a.f(«one», two, «three») b
 5646                a.f(«one», two, «three») b
 5647            "},
 5648        );
 5649
 5650        // Can't move earlier than the first tab stop
 5651        assert!(!editor.move_to_prev_snippet_tabstop(cx));
 5652        assert(
 5653            editor,
 5654            cx,
 5655            indoc! {"
 5656                a.f(«one», two, «three») b
 5657                a.f(«one», two, «three») b
 5658                a.f(«one», two, «three») b
 5659            "},
 5660        );
 5661
 5662        assert!(editor.move_to_next_snippet_tabstop(cx));
 5663        assert(
 5664            editor,
 5665            cx,
 5666            indoc! {"
 5667                a.f(one, «two», three) b
 5668                a.f(one, «two», three) b
 5669                a.f(one, «two», three) b
 5670            "},
 5671        );
 5672
 5673        editor.move_to_prev_snippet_tabstop(cx);
 5674        assert(
 5675            editor,
 5676            cx,
 5677            indoc! {"
 5678                a.f(«one», two, «three») b
 5679                a.f(«one», two, «three») b
 5680                a.f(«one», two, «three») b
 5681            "},
 5682        );
 5683
 5684        assert!(editor.move_to_next_snippet_tabstop(cx));
 5685        assert(
 5686            editor,
 5687            cx,
 5688            indoc! {"
 5689                a.f(one, «two», three) b
 5690                a.f(one, «two», three) b
 5691                a.f(one, «two», three) b
 5692            "},
 5693        );
 5694        assert!(editor.move_to_next_snippet_tabstop(cx));
 5695        assert(
 5696            editor,
 5697            cx,
 5698            indoc! {"
 5699                a.f(one, two, three)ˇ b
 5700                a.f(one, two, three)ˇ b
 5701                a.f(one, two, three)ˇ b
 5702            "},
 5703        );
 5704
 5705        // As soon as the last tab stop is reached, snippet state is gone
 5706        editor.move_to_prev_snippet_tabstop(cx);
 5707        assert(
 5708            editor,
 5709            cx,
 5710            indoc! {"
 5711                a.f(one, two, three)ˇ b
 5712                a.f(one, two, three)ˇ b
 5713                a.f(one, two, three)ˇ b
 5714            "},
 5715        );
 5716    });
 5717}
 5718
 5719#[gpui::test]
 5720async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
 5721    init_test(cx, |_| {});
 5722
 5723    let fs = FakeFs::new(cx.executor());
 5724    fs.insert_file("/file.rs", Default::default()).await;
 5725
 5726    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 5727
 5728    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 5729    language_registry.add(rust_lang());
 5730    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 5731        "Rust",
 5732        FakeLspAdapter {
 5733            capabilities: lsp::ServerCapabilities {
 5734                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 5735                ..Default::default()
 5736            },
 5737            ..Default::default()
 5738        },
 5739    );
 5740
 5741    let buffer = project
 5742        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 5743        .await
 5744        .unwrap();
 5745
 5746    cx.executor().start_waiting();
 5747    let fake_server = fake_servers.next().await.unwrap();
 5748
 5749    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5750    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5751    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 5752    assert!(cx.read(|cx| editor.is_dirty(cx)));
 5753
 5754    let save = editor
 5755        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5756        .unwrap();
 5757    fake_server
 5758        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 5759            assert_eq!(
 5760                params.text_document.uri,
 5761                lsp::Url::from_file_path("/file.rs").unwrap()
 5762            );
 5763            assert_eq!(params.options.tab_size, 4);
 5764            Ok(Some(vec![lsp::TextEdit::new(
 5765                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 5766                ", ".to_string(),
 5767            )]))
 5768        })
 5769        .next()
 5770        .await;
 5771    cx.executor().start_waiting();
 5772    save.await;
 5773
 5774    assert_eq!(
 5775        editor.update(cx, |editor, cx| editor.text(cx)),
 5776        "one, two\nthree\n"
 5777    );
 5778    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 5779
 5780    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 5781    assert!(cx.read(|cx| editor.is_dirty(cx)));
 5782
 5783    // Ensure we can still save even if formatting hangs.
 5784    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 5785        assert_eq!(
 5786            params.text_document.uri,
 5787            lsp::Url::from_file_path("/file.rs").unwrap()
 5788        );
 5789        futures::future::pending::<()>().await;
 5790        unreachable!()
 5791    });
 5792    let save = editor
 5793        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5794        .unwrap();
 5795    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 5796    cx.executor().start_waiting();
 5797    save.await;
 5798    assert_eq!(
 5799        editor.update(cx, |editor, cx| editor.text(cx)),
 5800        "one\ntwo\nthree\n"
 5801    );
 5802    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 5803
 5804    // For non-dirty buffer, no formatting request should be sent
 5805    let save = editor
 5806        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5807        .unwrap();
 5808    let _pending_format_request = fake_server
 5809        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 5810            panic!("Should not be invoked on non-dirty buffer");
 5811        })
 5812        .next();
 5813    cx.executor().start_waiting();
 5814    save.await;
 5815
 5816    // Set rust language override and assert overridden tabsize is sent to language server
 5817    update_test_language_settings(cx, |settings| {
 5818        settings.languages.insert(
 5819            "Rust".into(),
 5820            LanguageSettingsContent {
 5821                tab_size: NonZeroU32::new(8),
 5822                ..Default::default()
 5823            },
 5824        );
 5825    });
 5826
 5827    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
 5828    assert!(cx.read(|cx| editor.is_dirty(cx)));
 5829    let save = editor
 5830        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5831        .unwrap();
 5832    fake_server
 5833        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 5834            assert_eq!(
 5835                params.text_document.uri,
 5836                lsp::Url::from_file_path("/file.rs").unwrap()
 5837            );
 5838            assert_eq!(params.options.tab_size, 8);
 5839            Ok(Some(vec![]))
 5840        })
 5841        .next()
 5842        .await;
 5843    cx.executor().start_waiting();
 5844    save.await;
 5845}
 5846
 5847#[gpui::test]
 5848async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
 5849    init_test(cx, |_| {});
 5850
 5851    let cols = 4;
 5852    let rows = 10;
 5853    let sample_text_1 = sample_text(rows, cols, 'a');
 5854    assert_eq!(
 5855        sample_text_1,
 5856        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 5857    );
 5858    let sample_text_2 = sample_text(rows, cols, 'l');
 5859    assert_eq!(
 5860        sample_text_2,
 5861        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 5862    );
 5863    let sample_text_3 = sample_text(rows, cols, 'v');
 5864    assert_eq!(
 5865        sample_text_3,
 5866        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 5867    );
 5868
 5869    let fs = FakeFs::new(cx.executor());
 5870    fs.insert_tree(
 5871        "/a",
 5872        json!({
 5873            "main.rs": sample_text_1,
 5874            "other.rs": sample_text_2,
 5875            "lib.rs": sample_text_3,
 5876        }),
 5877    )
 5878    .await;
 5879
 5880    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 5881    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 5882    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 5883
 5884    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 5885    language_registry.add(rust_lang());
 5886    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 5887        "Rust",
 5888        FakeLspAdapter {
 5889            capabilities: lsp::ServerCapabilities {
 5890                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 5891                ..Default::default()
 5892            },
 5893            ..Default::default()
 5894        },
 5895    );
 5896
 5897    let worktree = project.update(cx, |project, _| {
 5898        let mut worktrees = project.worktrees().collect::<Vec<_>>();
 5899        assert_eq!(worktrees.len(), 1);
 5900        worktrees.pop().unwrap()
 5901    });
 5902    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 5903
 5904    let buffer_1 = project
 5905        .update(cx, |project, cx| {
 5906            project.open_buffer((worktree_id, "main.rs"), cx)
 5907        })
 5908        .await
 5909        .unwrap();
 5910    let buffer_2 = project
 5911        .update(cx, |project, cx| {
 5912            project.open_buffer((worktree_id, "other.rs"), cx)
 5913        })
 5914        .await
 5915        .unwrap();
 5916    let buffer_3 = project
 5917        .update(cx, |project, cx| {
 5918            project.open_buffer((worktree_id, "lib.rs"), cx)
 5919        })
 5920        .await
 5921        .unwrap();
 5922
 5923    let multi_buffer = cx.new_model(|cx| {
 5924        let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
 5925        multi_buffer.push_excerpts(
 5926            buffer_1.clone(),
 5927            [
 5928                ExcerptRange {
 5929                    context: Point::new(0, 0)..Point::new(3, 0),
 5930                    primary: None,
 5931                },
 5932                ExcerptRange {
 5933                    context: Point::new(5, 0)..Point::new(7, 0),
 5934                    primary: None,
 5935                },
 5936                ExcerptRange {
 5937                    context: Point::new(9, 0)..Point::new(10, 4),
 5938                    primary: None,
 5939                },
 5940            ],
 5941            cx,
 5942        );
 5943        multi_buffer.push_excerpts(
 5944            buffer_2.clone(),
 5945            [
 5946                ExcerptRange {
 5947                    context: Point::new(0, 0)..Point::new(3, 0),
 5948                    primary: None,
 5949                },
 5950                ExcerptRange {
 5951                    context: Point::new(5, 0)..Point::new(7, 0),
 5952                    primary: None,
 5953                },
 5954                ExcerptRange {
 5955                    context: Point::new(9, 0)..Point::new(10, 4),
 5956                    primary: None,
 5957                },
 5958            ],
 5959            cx,
 5960        );
 5961        multi_buffer.push_excerpts(
 5962            buffer_3.clone(),
 5963            [
 5964                ExcerptRange {
 5965                    context: Point::new(0, 0)..Point::new(3, 0),
 5966                    primary: None,
 5967                },
 5968                ExcerptRange {
 5969                    context: Point::new(5, 0)..Point::new(7, 0),
 5970                    primary: None,
 5971                },
 5972                ExcerptRange {
 5973                    context: Point::new(9, 0)..Point::new(10, 4),
 5974                    primary: None,
 5975                },
 5976            ],
 5977            cx,
 5978        );
 5979        multi_buffer
 5980    });
 5981    let multi_buffer_editor =
 5982        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
 5983
 5984    multi_buffer_editor.update(cx, |editor, cx| {
 5985        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 5986        editor.insert("|one|two|three|", cx);
 5987    });
 5988    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 5989    multi_buffer_editor.update(cx, |editor, cx| {
 5990        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 5991            s.select_ranges(Some(60..70))
 5992        });
 5993        editor.insert("|four|five|six|", cx);
 5994    });
 5995    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 5996
 5997    // First two buffers should be edited, but not the third one.
 5998    assert_eq!(
 5999        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 6000        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 6001    );
 6002    buffer_1.update(cx, |buffer, _| {
 6003        assert!(buffer.is_dirty());
 6004        assert_eq!(
 6005            buffer.text(),
 6006            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 6007        )
 6008    });
 6009    buffer_2.update(cx, |buffer, _| {
 6010        assert!(buffer.is_dirty());
 6011        assert_eq!(
 6012            buffer.text(),
 6013            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 6014        )
 6015    });
 6016    buffer_3.update(cx, |buffer, _| {
 6017        assert!(!buffer.is_dirty());
 6018        assert_eq!(buffer.text(), sample_text_3,)
 6019    });
 6020
 6021    cx.executor().start_waiting();
 6022    let save = multi_buffer_editor
 6023        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6024        .unwrap();
 6025
 6026    let fake_server = fake_servers.next().await.unwrap();
 6027    fake_server
 6028        .server
 6029        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6030            Ok(Some(vec![lsp::TextEdit::new(
 6031                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6032                format!("[{} formatted]", params.text_document.uri),
 6033            )]))
 6034        })
 6035        .detach();
 6036    save.await;
 6037
 6038    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 6039    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 6040    assert_eq!(
 6041        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 6042        "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
 6043    );
 6044    buffer_1.update(cx, |buffer, _| {
 6045        assert!(!buffer.is_dirty());
 6046        assert_eq!(
 6047            buffer.text(),
 6048            "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
 6049        )
 6050    });
 6051    buffer_2.update(cx, |buffer, _| {
 6052        assert!(!buffer.is_dirty());
 6053        assert_eq!(
 6054            buffer.text(),
 6055            "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
 6056        )
 6057    });
 6058    buffer_3.update(cx, |buffer, _| {
 6059        assert!(!buffer.is_dirty());
 6060        assert_eq!(buffer.text(), sample_text_3,)
 6061    });
 6062}
 6063
 6064#[gpui::test]
 6065async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
 6066    init_test(cx, |_| {});
 6067
 6068    let fs = FakeFs::new(cx.executor());
 6069    fs.insert_file("/file.rs", Default::default()).await;
 6070
 6071    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6072
 6073    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6074    language_registry.add(rust_lang());
 6075    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6076        "Rust",
 6077        FakeLspAdapter {
 6078            capabilities: lsp::ServerCapabilities {
 6079                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 6080                ..Default::default()
 6081            },
 6082            ..Default::default()
 6083        },
 6084    );
 6085
 6086    let buffer = project
 6087        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6088        .await
 6089        .unwrap();
 6090
 6091    cx.executor().start_waiting();
 6092    let fake_server = fake_servers.next().await.unwrap();
 6093
 6094    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6095    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6096    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6097    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6098
 6099    let save = editor
 6100        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6101        .unwrap();
 6102    fake_server
 6103        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 6104            assert_eq!(
 6105                params.text_document.uri,
 6106                lsp::Url::from_file_path("/file.rs").unwrap()
 6107            );
 6108            assert_eq!(params.options.tab_size, 4);
 6109            Ok(Some(vec![lsp::TextEdit::new(
 6110                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6111                ", ".to_string(),
 6112            )]))
 6113        })
 6114        .next()
 6115        .await;
 6116    cx.executor().start_waiting();
 6117    save.await;
 6118    assert_eq!(
 6119        editor.update(cx, |editor, cx| editor.text(cx)),
 6120        "one, two\nthree\n"
 6121    );
 6122    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6123
 6124    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6125    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6126
 6127    // Ensure we can still save even if formatting hangs.
 6128    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
 6129        move |params, _| async move {
 6130            assert_eq!(
 6131                params.text_document.uri,
 6132                lsp::Url::from_file_path("/file.rs").unwrap()
 6133            );
 6134            futures::future::pending::<()>().await;
 6135            unreachable!()
 6136        },
 6137    );
 6138    let save = editor
 6139        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6140        .unwrap();
 6141    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6142    cx.executor().start_waiting();
 6143    save.await;
 6144    assert_eq!(
 6145        editor.update(cx, |editor, cx| editor.text(cx)),
 6146        "one\ntwo\nthree\n"
 6147    );
 6148    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6149
 6150    // For non-dirty buffer, no formatting request should be sent
 6151    let save = editor
 6152        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6153        .unwrap();
 6154    let _pending_format_request = fake_server
 6155        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 6156            panic!("Should not be invoked on non-dirty buffer");
 6157        })
 6158        .next();
 6159    cx.executor().start_waiting();
 6160    save.await;
 6161
 6162    // Set Rust language override and assert overridden tabsize is sent to language server
 6163    update_test_language_settings(cx, |settings| {
 6164        settings.languages.insert(
 6165            "Rust".into(),
 6166            LanguageSettingsContent {
 6167                tab_size: NonZeroU32::new(8),
 6168                ..Default::default()
 6169            },
 6170        );
 6171    });
 6172
 6173    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
 6174    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6175    let save = editor
 6176        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6177        .unwrap();
 6178    fake_server
 6179        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 6180            assert_eq!(
 6181                params.text_document.uri,
 6182                lsp::Url::from_file_path("/file.rs").unwrap()
 6183            );
 6184            assert_eq!(params.options.tab_size, 8);
 6185            Ok(Some(vec![]))
 6186        })
 6187        .next()
 6188        .await;
 6189    cx.executor().start_waiting();
 6190    save.await;
 6191}
 6192
 6193#[gpui::test]
 6194async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
 6195    init_test(cx, |settings| {
 6196        settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
 6197    });
 6198
 6199    let fs = FakeFs::new(cx.executor());
 6200    fs.insert_file("/file.rs", Default::default()).await;
 6201
 6202    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6203
 6204    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6205    language_registry.add(Arc::new(Language::new(
 6206        LanguageConfig {
 6207            name: "Rust".into(),
 6208            matcher: LanguageMatcher {
 6209                path_suffixes: vec!["rs".to_string()],
 6210                ..Default::default()
 6211            },
 6212            // Enable Prettier formatting for the same buffer, and ensure
 6213            // LSP is called instead of Prettier.
 6214            prettier_parser_name: Some("test_parser".to_string()),
 6215            ..Default::default()
 6216        },
 6217        Some(tree_sitter_rust::language()),
 6218    )));
 6219    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6220        "Rust",
 6221        FakeLspAdapter {
 6222            capabilities: lsp::ServerCapabilities {
 6223                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6224                ..Default::default()
 6225            },
 6226            ..Default::default()
 6227        },
 6228    );
 6229
 6230    let buffer = project
 6231        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6232        .await
 6233        .unwrap();
 6234
 6235    cx.executor().start_waiting();
 6236    let fake_server = fake_servers.next().await.unwrap();
 6237
 6238    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6239    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6240    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6241
 6242    let format = editor
 6243        .update(cx, |editor, cx| {
 6244            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 6245        })
 6246        .unwrap();
 6247    fake_server
 6248        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6249            assert_eq!(
 6250                params.text_document.uri,
 6251                lsp::Url::from_file_path("/file.rs").unwrap()
 6252            );
 6253            assert_eq!(params.options.tab_size, 4);
 6254            Ok(Some(vec![lsp::TextEdit::new(
 6255                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6256                ", ".to_string(),
 6257            )]))
 6258        })
 6259        .next()
 6260        .await;
 6261    cx.executor().start_waiting();
 6262    format.await;
 6263    assert_eq!(
 6264        editor.update(cx, |editor, cx| editor.text(cx)),
 6265        "one, two\nthree\n"
 6266    );
 6267
 6268    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6269    // Ensure we don't lock if formatting hangs.
 6270    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6271        assert_eq!(
 6272            params.text_document.uri,
 6273            lsp::Url::from_file_path("/file.rs").unwrap()
 6274        );
 6275        futures::future::pending::<()>().await;
 6276        unreachable!()
 6277    });
 6278    let format = editor
 6279        .update(cx, |editor, cx| {
 6280            editor.perform_format(project, FormatTrigger::Manual, cx)
 6281        })
 6282        .unwrap();
 6283    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6284    cx.executor().start_waiting();
 6285    format.await;
 6286    assert_eq!(
 6287        editor.update(cx, |editor, cx| editor.text(cx)),
 6288        "one\ntwo\nthree\n"
 6289    );
 6290}
 6291
 6292#[gpui::test]
 6293async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
 6294    init_test(cx, |_| {});
 6295
 6296    let mut cx = EditorLspTestContext::new_rust(
 6297        lsp::ServerCapabilities {
 6298            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6299            ..Default::default()
 6300        },
 6301        cx,
 6302    )
 6303    .await;
 6304
 6305    cx.set_state(indoc! {"
 6306        one.twoˇ
 6307    "});
 6308
 6309    // The format request takes a long time. When it completes, it inserts
 6310    // a newline and an indent before the `.`
 6311    cx.lsp
 6312        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
 6313            let executor = cx.background_executor().clone();
 6314            async move {
 6315                executor.timer(Duration::from_millis(100)).await;
 6316                Ok(Some(vec![lsp::TextEdit {
 6317                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 6318                    new_text: "\n    ".into(),
 6319                }]))
 6320            }
 6321        });
 6322
 6323    // Submit a format request.
 6324    let format_1 = cx
 6325        .update_editor(|editor, cx| editor.format(&Format, cx))
 6326        .unwrap();
 6327    cx.executor().run_until_parked();
 6328
 6329    // Submit a second format request.
 6330    let format_2 = cx
 6331        .update_editor(|editor, cx| editor.format(&Format, cx))
 6332        .unwrap();
 6333    cx.executor().run_until_parked();
 6334
 6335    // Wait for both format requests to complete
 6336    cx.executor().advance_clock(Duration::from_millis(200));
 6337    cx.executor().start_waiting();
 6338    format_1.await.unwrap();
 6339    cx.executor().start_waiting();
 6340    format_2.await.unwrap();
 6341
 6342    // The formatting edits only happens once.
 6343    cx.assert_editor_state(indoc! {"
 6344        one
 6345            .twoˇ
 6346    "});
 6347}
 6348
 6349#[gpui::test]
 6350async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
 6351    init_test(cx, |settings| {
 6352        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 6353    });
 6354
 6355    let mut cx = EditorLspTestContext::new_rust(
 6356        lsp::ServerCapabilities {
 6357            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6358            ..Default::default()
 6359        },
 6360        cx,
 6361    )
 6362    .await;
 6363
 6364    // Set up a buffer white some trailing whitespace and no trailing newline.
 6365    cx.set_state(
 6366        &[
 6367            "one ",   //
 6368            "twoˇ",   //
 6369            "three ", //
 6370            "four",   //
 6371        ]
 6372        .join("\n"),
 6373    );
 6374
 6375    // Submit a format request.
 6376    let format = cx
 6377        .update_editor(|editor, cx| editor.format(&Format, cx))
 6378        .unwrap();
 6379
 6380    // Record which buffer changes have been sent to the language server
 6381    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 6382    cx.lsp
 6383        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 6384            let buffer_changes = buffer_changes.clone();
 6385            move |params, _| {
 6386                buffer_changes.lock().extend(
 6387                    params
 6388                        .content_changes
 6389                        .into_iter()
 6390                        .map(|e| (e.range.unwrap(), e.text)),
 6391                );
 6392            }
 6393        });
 6394
 6395    // Handle formatting requests to the language server.
 6396    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
 6397        let buffer_changes = buffer_changes.clone();
 6398        move |_, _| {
 6399            // When formatting is requested, trailing whitespace has already been stripped,
 6400            // and the trailing newline has already been added.
 6401            assert_eq!(
 6402                &buffer_changes.lock()[1..],
 6403                &[
 6404                    (
 6405                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 6406                        "".into()
 6407                    ),
 6408                    (
 6409                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 6410                        "".into()
 6411                    ),
 6412                    (
 6413                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 6414                        "\n".into()
 6415                    ),
 6416                ]
 6417            );
 6418
 6419            // Insert blank lines between each line of the buffer.
 6420            async move {
 6421                Ok(Some(vec![
 6422                    lsp::TextEdit {
 6423                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
 6424                        new_text: "\n".into(),
 6425                    },
 6426                    lsp::TextEdit {
 6427                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
 6428                        new_text: "\n".into(),
 6429                    },
 6430                ]))
 6431            }
 6432        }
 6433    });
 6434
 6435    // After formatting the buffer, the trailing whitespace is stripped,
 6436    // a newline is appended, and the edits provided by the language server
 6437    // have been applied.
 6438    format.await.unwrap();
 6439    cx.assert_editor_state(
 6440        &[
 6441            "one",   //
 6442            "",      //
 6443            "twoˇ",  //
 6444            "",      //
 6445            "three", //
 6446            "four",  //
 6447            "",      //
 6448        ]
 6449        .join("\n"),
 6450    );
 6451
 6452    // Undoing the formatting undoes the trailing whitespace removal, the
 6453    // trailing newline, and the LSP edits.
 6454    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 6455    cx.assert_editor_state(
 6456        &[
 6457            "one ",   //
 6458            "twoˇ",   //
 6459            "three ", //
 6460            "four",   //
 6461        ]
 6462        .join("\n"),
 6463    );
 6464}
 6465
 6466#[gpui::test]
 6467async fn test_completion(cx: &mut gpui::TestAppContext) {
 6468    init_test(cx, |_| {});
 6469
 6470    let mut cx = EditorLspTestContext::new_rust(
 6471        lsp::ServerCapabilities {
 6472            completion_provider: Some(lsp::CompletionOptions {
 6473                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 6474                resolve_provider: Some(true),
 6475                ..Default::default()
 6476            }),
 6477            ..Default::default()
 6478        },
 6479        cx,
 6480    )
 6481    .await;
 6482
 6483    cx.set_state(indoc! {"
 6484        oneˇ
 6485        two
 6486        three
 6487    "});
 6488    cx.simulate_keystroke(".");
 6489    handle_completion_request(
 6490        &mut cx,
 6491        indoc! {"
 6492            one.|<>
 6493            two
 6494            three
 6495        "},
 6496        vec!["first_completion", "second_completion"],
 6497    )
 6498    .await;
 6499    cx.condition(|editor, _| editor.context_menu_visible())
 6500        .await;
 6501    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6502        editor.context_menu_next(&Default::default(), cx);
 6503        editor
 6504            .confirm_completion(&ConfirmCompletion::default(), cx)
 6505            .unwrap()
 6506    });
 6507    cx.assert_editor_state(indoc! {"
 6508        one.second_completionˇ
 6509        two
 6510        three
 6511    "});
 6512
 6513    handle_resolve_completion_request(
 6514        &mut cx,
 6515        Some(vec![
 6516            (
 6517                //This overlaps with the primary completion edit which is
 6518                //misbehavior from the LSP spec, test that we filter it out
 6519                indoc! {"
 6520                    one.second_ˇcompletion
 6521                    two
 6522                    threeˇ
 6523                "},
 6524                "overlapping additional edit",
 6525            ),
 6526            (
 6527                indoc! {"
 6528                    one.second_completion
 6529                    two
 6530                    threeˇ
 6531                "},
 6532                "\nadditional edit",
 6533            ),
 6534        ]),
 6535    )
 6536    .await;
 6537    apply_additional_edits.await.unwrap();
 6538    cx.assert_editor_state(indoc! {"
 6539        one.second_completionˇ
 6540        two
 6541        three
 6542        additional edit
 6543    "});
 6544
 6545    cx.set_state(indoc! {"
 6546        one.second_completion
 6547        twoˇ
 6548        threeˇ
 6549        additional edit
 6550    "});
 6551    cx.simulate_keystroke(" ");
 6552    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6553    cx.simulate_keystroke("s");
 6554    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6555
 6556    cx.assert_editor_state(indoc! {"
 6557        one.second_completion
 6558        two sˇ
 6559        three sˇ
 6560        additional edit
 6561    "});
 6562    handle_completion_request(
 6563        &mut cx,
 6564        indoc! {"
 6565            one.second_completion
 6566            two s
 6567            three <s|>
 6568            additional edit
 6569        "},
 6570        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 6571    )
 6572    .await;
 6573    cx.condition(|editor, _| editor.context_menu_visible())
 6574        .await;
 6575
 6576    cx.simulate_keystroke("i");
 6577
 6578    handle_completion_request(
 6579        &mut cx,
 6580        indoc! {"
 6581            one.second_completion
 6582            two si
 6583            three <si|>
 6584            additional edit
 6585        "},
 6586        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 6587    )
 6588    .await;
 6589    cx.condition(|editor, _| editor.context_menu_visible())
 6590        .await;
 6591
 6592    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6593        editor
 6594            .confirm_completion(&ConfirmCompletion::default(), cx)
 6595            .unwrap()
 6596    });
 6597    cx.assert_editor_state(indoc! {"
 6598        one.second_completion
 6599        two sixth_completionˇ
 6600        three sixth_completionˇ
 6601        additional edit
 6602    "});
 6603
 6604    handle_resolve_completion_request(&mut cx, None).await;
 6605    apply_additional_edits.await.unwrap();
 6606
 6607    _ = cx.update(|cx| {
 6608        cx.update_global::<SettingsStore, _>(|settings, cx| {
 6609            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 6610                settings.show_completions_on_input = Some(false);
 6611            });
 6612        })
 6613    });
 6614    cx.set_state("editorˇ");
 6615    cx.simulate_keystroke(".");
 6616    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6617    cx.simulate_keystroke("c");
 6618    cx.simulate_keystroke("l");
 6619    cx.simulate_keystroke("o");
 6620    cx.assert_editor_state("editor.cloˇ");
 6621    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6622    cx.update_editor(|editor, cx| {
 6623        editor.show_completions(&ShowCompletions, cx);
 6624    });
 6625    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
 6626    cx.condition(|editor, _| editor.context_menu_visible())
 6627        .await;
 6628    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6629        editor
 6630            .confirm_completion(&ConfirmCompletion::default(), cx)
 6631            .unwrap()
 6632    });
 6633    cx.assert_editor_state("editor.closeˇ");
 6634    handle_resolve_completion_request(&mut cx, None).await;
 6635    apply_additional_edits.await.unwrap();
 6636}
 6637
 6638#[gpui::test]
 6639async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
 6640    init_test(cx, |_| {});
 6641    let mut cx = EditorTestContext::new(cx).await;
 6642    let language = Arc::new(Language::new(
 6643        LanguageConfig {
 6644            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
 6645            ..Default::default()
 6646        },
 6647        Some(tree_sitter_rust::language()),
 6648    ));
 6649    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6650
 6651    // If multiple selections intersect a line, the line is only toggled once.
 6652    cx.set_state(indoc! {"
 6653        fn a() {
 6654            «//b();
 6655            ˇ»// «c();
 6656            //ˇ»  d();
 6657        }
 6658    "});
 6659
 6660    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6661
 6662    cx.assert_editor_state(indoc! {"
 6663        fn a() {
 6664            «b();
 6665            c();
 6666            ˇ» d();
 6667        }
 6668    "});
 6669
 6670    // The comment prefix is inserted at the same column for every line in a
 6671    // selection.
 6672    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6673
 6674    cx.assert_editor_state(indoc! {"
 6675        fn a() {
 6676            // «b();
 6677            // c();
 6678            ˇ»//  d();
 6679        }
 6680    "});
 6681
 6682    // If a selection ends at the beginning of a line, that line is not toggled.
 6683    cx.set_selections_state(indoc! {"
 6684        fn a() {
 6685            // b();
 6686            «// c();
 6687        ˇ»    //  d();
 6688        }
 6689    "});
 6690
 6691    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6692
 6693    cx.assert_editor_state(indoc! {"
 6694        fn a() {
 6695            // b();
 6696            «c();
 6697        ˇ»    //  d();
 6698        }
 6699    "});
 6700
 6701    // If a selection span a single line and is empty, the line is toggled.
 6702    cx.set_state(indoc! {"
 6703        fn a() {
 6704            a();
 6705            b();
 6706        ˇ
 6707        }
 6708    "});
 6709
 6710    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6711
 6712    cx.assert_editor_state(indoc! {"
 6713        fn a() {
 6714            a();
 6715            b();
 6716        //•ˇ
 6717        }
 6718    "});
 6719
 6720    // If a selection span multiple lines, empty lines are not toggled.
 6721    cx.set_state(indoc! {"
 6722        fn a() {
 6723            «a();
 6724
 6725            c();ˇ»
 6726        }
 6727    "});
 6728
 6729    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6730
 6731    cx.assert_editor_state(indoc! {"
 6732        fn a() {
 6733            // «a();
 6734
 6735            // c();ˇ»
 6736        }
 6737    "});
 6738
 6739    // If a selection includes multiple comment prefixes, all lines are uncommented.
 6740    cx.set_state(indoc! {"
 6741        fn a() {
 6742            «// a();
 6743            /// b();
 6744            //! c();ˇ»
 6745        }
 6746    "});
 6747
 6748    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6749
 6750    cx.assert_editor_state(indoc! {"
 6751        fn a() {
 6752            «a();
 6753            b();
 6754            c();ˇ»
 6755        }
 6756    "});
 6757}
 6758
 6759#[gpui::test]
 6760async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
 6761    init_test(cx, |_| {});
 6762
 6763    let language = Arc::new(Language::new(
 6764        LanguageConfig {
 6765            line_comments: vec!["// ".into()],
 6766            ..Default::default()
 6767        },
 6768        Some(tree_sitter_rust::language()),
 6769    ));
 6770
 6771    let mut cx = EditorTestContext::new(cx).await;
 6772
 6773    cx.language_registry().add(language.clone());
 6774    cx.update_buffer(|buffer, cx| {
 6775        buffer.set_language(Some(language), cx);
 6776    });
 6777
 6778    let toggle_comments = &ToggleComments {
 6779        advance_downwards: true,
 6780    };
 6781
 6782    // Single cursor on one line -> advance
 6783    // Cursor moves horizontally 3 characters as well on non-blank line
 6784    cx.set_state(indoc!(
 6785        "fn a() {
 6786             ˇdog();
 6787             cat();
 6788        }"
 6789    ));
 6790    cx.update_editor(|editor, cx| {
 6791        editor.toggle_comments(toggle_comments, cx);
 6792    });
 6793    cx.assert_editor_state(indoc!(
 6794        "fn a() {
 6795             // dog();
 6796             catˇ();
 6797        }"
 6798    ));
 6799
 6800    // Single selection on one line -> don't advance
 6801    cx.set_state(indoc!(
 6802        "fn a() {
 6803             «dog()ˇ»;
 6804             cat();
 6805        }"
 6806    ));
 6807    cx.update_editor(|editor, cx| {
 6808        editor.toggle_comments(toggle_comments, cx);
 6809    });
 6810    cx.assert_editor_state(indoc!(
 6811        "fn a() {
 6812             // «dog()ˇ»;
 6813             cat();
 6814        }"
 6815    ));
 6816
 6817    // Multiple cursors on one line -> advance
 6818    cx.set_state(indoc!(
 6819        "fn a() {
 6820             ˇdˇog();
 6821             cat();
 6822        }"
 6823    ));
 6824    cx.update_editor(|editor, cx| {
 6825        editor.toggle_comments(toggle_comments, cx);
 6826    });
 6827    cx.assert_editor_state(indoc!(
 6828        "fn a() {
 6829             // dog();
 6830             catˇ(ˇ);
 6831        }"
 6832    ));
 6833
 6834    // Multiple cursors on one line, with selection -> don't advance
 6835    cx.set_state(indoc!(
 6836        "fn a() {
 6837             ˇdˇog«()ˇ»;
 6838             cat();
 6839        }"
 6840    ));
 6841    cx.update_editor(|editor, cx| {
 6842        editor.toggle_comments(toggle_comments, cx);
 6843    });
 6844    cx.assert_editor_state(indoc!(
 6845        "fn a() {
 6846             // ˇdˇog«()ˇ»;
 6847             cat();
 6848        }"
 6849    ));
 6850
 6851    // Single cursor on one line -> advance
 6852    // Cursor moves to column 0 on blank line
 6853    cx.set_state(indoc!(
 6854        "fn a() {
 6855             ˇdog();
 6856
 6857             cat();
 6858        }"
 6859    ));
 6860    cx.update_editor(|editor, cx| {
 6861        editor.toggle_comments(toggle_comments, cx);
 6862    });
 6863    cx.assert_editor_state(indoc!(
 6864        "fn a() {
 6865             // dog();
 6866        ˇ
 6867             cat();
 6868        }"
 6869    ));
 6870
 6871    // Single cursor on one line -> advance
 6872    // Cursor starts and ends at column 0
 6873    cx.set_state(indoc!(
 6874        "fn a() {
 6875         ˇ    dog();
 6876             cat();
 6877        }"
 6878    ));
 6879    cx.update_editor(|editor, cx| {
 6880        editor.toggle_comments(toggle_comments, cx);
 6881    });
 6882    cx.assert_editor_state(indoc!(
 6883        "fn a() {
 6884             // dog();
 6885         ˇ    cat();
 6886        }"
 6887    ));
 6888}
 6889
 6890#[gpui::test]
 6891async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 6892    init_test(cx, |_| {});
 6893
 6894    let mut cx = EditorTestContext::new(cx).await;
 6895
 6896    let html_language = Arc::new(
 6897        Language::new(
 6898            LanguageConfig {
 6899                name: "HTML".into(),
 6900                block_comment: Some(("<!-- ".into(), " -->".into())),
 6901                ..Default::default()
 6902            },
 6903            Some(tree_sitter_html::language()),
 6904        )
 6905        .with_injection_query(
 6906            r#"
 6907            (script_element
 6908                (raw_text) @content
 6909                (#set! "language" "javascript"))
 6910            "#,
 6911        )
 6912        .unwrap(),
 6913    );
 6914
 6915    let javascript_language = Arc::new(Language::new(
 6916        LanguageConfig {
 6917            name: "JavaScript".into(),
 6918            line_comments: vec!["// ".into()],
 6919            ..Default::default()
 6920        },
 6921        Some(tree_sitter_typescript::language_tsx()),
 6922    ));
 6923
 6924    cx.language_registry().add(html_language.clone());
 6925    cx.language_registry().add(javascript_language.clone());
 6926    cx.update_buffer(|buffer, cx| {
 6927        buffer.set_language(Some(html_language), cx);
 6928    });
 6929
 6930    // Toggle comments for empty selections
 6931    cx.set_state(
 6932        &r#"
 6933            <p>A</p>ˇ
 6934            <p>B</p>ˇ
 6935            <p>C</p>ˇ
 6936        "#
 6937        .unindent(),
 6938    );
 6939    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6940    cx.assert_editor_state(
 6941        &r#"
 6942            <!-- <p>A</p>ˇ -->
 6943            <!-- <p>B</p>ˇ -->
 6944            <!-- <p>C</p>ˇ -->
 6945        "#
 6946        .unindent(),
 6947    );
 6948    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6949    cx.assert_editor_state(
 6950        &r#"
 6951            <p>A</p>ˇ
 6952            <p>B</p>ˇ
 6953            <p>C</p>ˇ
 6954        "#
 6955        .unindent(),
 6956    );
 6957
 6958    // Toggle comments for mixture of empty and non-empty selections, where
 6959    // multiple selections occupy a given line.
 6960    cx.set_state(
 6961        &r#"
 6962            <p>A«</p>
 6963            <p>ˇ»B</p>ˇ
 6964            <p>C«</p>
 6965            <p>ˇ»D</p>ˇ
 6966        "#
 6967        .unindent(),
 6968    );
 6969
 6970    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6971    cx.assert_editor_state(
 6972        &r#"
 6973            <!-- <p>A«</p>
 6974            <p>ˇ»B</p>ˇ -->
 6975            <!-- <p>C«</p>
 6976            <p>ˇ»D</p>ˇ -->
 6977        "#
 6978        .unindent(),
 6979    );
 6980    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6981    cx.assert_editor_state(
 6982        &r#"
 6983            <p>A«</p>
 6984            <p>ˇ»B</p>ˇ
 6985            <p>C«</p>
 6986            <p>ˇ»D</p>ˇ
 6987        "#
 6988        .unindent(),
 6989    );
 6990
 6991    // Toggle comments when different languages are active for different
 6992    // selections.
 6993    cx.set_state(
 6994        &r#"
 6995            ˇ<script>
 6996                ˇvar x = new Y();
 6997            ˇ</script>
 6998        "#
 6999        .unindent(),
 7000    );
 7001    cx.executor().run_until_parked();
 7002    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 7003    cx.assert_editor_state(
 7004        &r#"
 7005            <!-- ˇ<script> -->
 7006                // ˇvar x = new Y();
 7007            <!-- ˇ</script> -->
 7008        "#
 7009        .unindent(),
 7010    );
 7011}
 7012
 7013#[gpui::test]
 7014fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
 7015    init_test(cx, |_| {});
 7016
 7017    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7018    let multibuffer = cx.new_model(|cx| {
 7019        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7020        multibuffer.push_excerpts(
 7021            buffer.clone(),
 7022            [
 7023                ExcerptRange {
 7024                    context: Point::new(0, 0)..Point::new(0, 4),
 7025                    primary: None,
 7026                },
 7027                ExcerptRange {
 7028                    context: Point::new(1, 0)..Point::new(1, 4),
 7029                    primary: None,
 7030                },
 7031            ],
 7032            cx,
 7033        );
 7034        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
 7035        multibuffer
 7036    });
 7037
 7038    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7039    _ = view.update(cx, |view, cx| {
 7040        assert_eq!(view.text(cx), "aaaa\nbbbb");
 7041        view.change_selections(None, cx, |s| {
 7042            s.select_ranges([
 7043                Point::new(0, 0)..Point::new(0, 0),
 7044                Point::new(1, 0)..Point::new(1, 0),
 7045            ])
 7046        });
 7047
 7048        view.handle_input("X", cx);
 7049        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
 7050        assert_eq!(
 7051            view.selections.ranges(cx),
 7052            [
 7053                Point::new(0, 1)..Point::new(0, 1),
 7054                Point::new(1, 1)..Point::new(1, 1),
 7055            ]
 7056        );
 7057
 7058        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
 7059        view.change_selections(None, cx, |s| {
 7060            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
 7061        });
 7062        view.backspace(&Default::default(), cx);
 7063        assert_eq!(view.text(cx), "Xa\nbbb");
 7064        assert_eq!(
 7065            view.selections.ranges(cx),
 7066            [Point::new(1, 0)..Point::new(1, 0)]
 7067        );
 7068
 7069        view.change_selections(None, cx, |s| {
 7070            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
 7071        });
 7072        view.backspace(&Default::default(), cx);
 7073        assert_eq!(view.text(cx), "X\nbb");
 7074        assert_eq!(
 7075            view.selections.ranges(cx),
 7076            [Point::new(0, 1)..Point::new(0, 1)]
 7077        );
 7078    });
 7079}
 7080
 7081#[gpui::test]
 7082fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
 7083    init_test(cx, |_| {});
 7084
 7085    let markers = vec![('[', ']').into(), ('(', ')').into()];
 7086    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
 7087        indoc! {"
 7088            [aaaa
 7089            (bbbb]
 7090            cccc)",
 7091        },
 7092        markers.clone(),
 7093    );
 7094    let excerpt_ranges = markers.into_iter().map(|marker| {
 7095        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
 7096        ExcerptRange {
 7097            context,
 7098            primary: None,
 7099        }
 7100    });
 7101    let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
 7102    let multibuffer = cx.new_model(|cx| {
 7103        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7104        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
 7105        multibuffer
 7106    });
 7107
 7108    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7109    _ = view.update(cx, |view, cx| {
 7110        let (expected_text, selection_ranges) = marked_text_ranges(
 7111            indoc! {"
 7112                aaaa
 7113                bˇbbb
 7114                bˇbbˇb
 7115                cccc"
 7116            },
 7117            true,
 7118        );
 7119        assert_eq!(view.text(cx), expected_text);
 7120        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
 7121
 7122        view.handle_input("X", cx);
 7123
 7124        let (expected_text, expected_selections) = marked_text_ranges(
 7125            indoc! {"
 7126                aaaa
 7127                bXˇbbXb
 7128                bXˇbbXˇb
 7129                cccc"
 7130            },
 7131            false,
 7132        );
 7133        assert_eq!(view.text(cx), expected_text);
 7134        assert_eq!(view.selections.ranges(cx), expected_selections);
 7135
 7136        view.newline(&Newline, cx);
 7137        let (expected_text, expected_selections) = marked_text_ranges(
 7138            indoc! {"
 7139                aaaa
 7140                bX
 7141                ˇbbX
 7142                b
 7143                bX
 7144                ˇbbX
 7145                ˇb
 7146                cccc"
 7147            },
 7148            false,
 7149        );
 7150        assert_eq!(view.text(cx), expected_text);
 7151        assert_eq!(view.selections.ranges(cx), expected_selections);
 7152    });
 7153}
 7154
 7155#[gpui::test]
 7156fn test_refresh_selections(cx: &mut TestAppContext) {
 7157    init_test(cx, |_| {});
 7158
 7159    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7160    let mut excerpt1_id = None;
 7161    let multibuffer = cx.new_model(|cx| {
 7162        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7163        excerpt1_id = multibuffer
 7164            .push_excerpts(
 7165                buffer.clone(),
 7166                [
 7167                    ExcerptRange {
 7168                        context: Point::new(0, 0)..Point::new(1, 4),
 7169                        primary: None,
 7170                    },
 7171                    ExcerptRange {
 7172                        context: Point::new(1, 0)..Point::new(2, 4),
 7173                        primary: None,
 7174                    },
 7175                ],
 7176                cx,
 7177            )
 7178            .into_iter()
 7179            .next();
 7180        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7181        multibuffer
 7182    });
 7183
 7184    let editor = cx.add_window(|cx| {
 7185        let mut editor = build_editor(multibuffer.clone(), cx);
 7186        let snapshot = editor.snapshot(cx);
 7187        editor.change_selections(None, cx, |s| {
 7188            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
 7189        });
 7190        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
 7191        assert_eq!(
 7192            editor.selections.ranges(cx),
 7193            [
 7194                Point::new(1, 3)..Point::new(1, 3),
 7195                Point::new(2, 1)..Point::new(2, 1),
 7196            ]
 7197        );
 7198        editor
 7199    });
 7200
 7201    // Refreshing selections is a no-op when excerpts haven't changed.
 7202    _ = editor.update(cx, |editor, cx| {
 7203        editor.change_selections(None, cx, |s| s.refresh());
 7204        assert_eq!(
 7205            editor.selections.ranges(cx),
 7206            [
 7207                Point::new(1, 3)..Point::new(1, 3),
 7208                Point::new(2, 1)..Point::new(2, 1),
 7209            ]
 7210        );
 7211    });
 7212
 7213    _ = multibuffer.update(cx, |multibuffer, cx| {
 7214        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7215    });
 7216    _ = editor.update(cx, |editor, cx| {
 7217        // Removing an excerpt causes the first selection to become degenerate.
 7218        assert_eq!(
 7219            editor.selections.ranges(cx),
 7220            [
 7221                Point::new(0, 0)..Point::new(0, 0),
 7222                Point::new(0, 1)..Point::new(0, 1)
 7223            ]
 7224        );
 7225
 7226        // Refreshing selections will relocate the first selection to the original buffer
 7227        // location.
 7228        editor.change_selections(None, cx, |s| s.refresh());
 7229        assert_eq!(
 7230            editor.selections.ranges(cx),
 7231            [
 7232                Point::new(0, 1)..Point::new(0, 1),
 7233                Point::new(0, 3)..Point::new(0, 3)
 7234            ]
 7235        );
 7236        assert!(editor.selections.pending_anchor().is_some());
 7237    });
 7238}
 7239
 7240#[gpui::test]
 7241fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
 7242    init_test(cx, |_| {});
 7243
 7244    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7245    let mut excerpt1_id = None;
 7246    let multibuffer = cx.new_model(|cx| {
 7247        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7248        excerpt1_id = multibuffer
 7249            .push_excerpts(
 7250                buffer.clone(),
 7251                [
 7252                    ExcerptRange {
 7253                        context: Point::new(0, 0)..Point::new(1, 4),
 7254                        primary: None,
 7255                    },
 7256                    ExcerptRange {
 7257                        context: Point::new(1, 0)..Point::new(2, 4),
 7258                        primary: None,
 7259                    },
 7260                ],
 7261                cx,
 7262            )
 7263            .into_iter()
 7264            .next();
 7265        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7266        multibuffer
 7267    });
 7268
 7269    let editor = cx.add_window(|cx| {
 7270        let mut editor = build_editor(multibuffer.clone(), cx);
 7271        let snapshot = editor.snapshot(cx);
 7272        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
 7273        assert_eq!(
 7274            editor.selections.ranges(cx),
 7275            [Point::new(1, 3)..Point::new(1, 3)]
 7276        );
 7277        editor
 7278    });
 7279
 7280    _ = multibuffer.update(cx, |multibuffer, cx| {
 7281        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7282    });
 7283    _ = editor.update(cx, |editor, cx| {
 7284        assert_eq!(
 7285            editor.selections.ranges(cx),
 7286            [Point::new(0, 0)..Point::new(0, 0)]
 7287        );
 7288
 7289        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
 7290        editor.change_selections(None, cx, |s| s.refresh());
 7291        assert_eq!(
 7292            editor.selections.ranges(cx),
 7293            [Point::new(0, 3)..Point::new(0, 3)]
 7294        );
 7295        assert!(editor.selections.pending_anchor().is_some());
 7296    });
 7297}
 7298
 7299#[gpui::test]
 7300async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 7301    init_test(cx, |_| {});
 7302
 7303    let language = Arc::new(
 7304        Language::new(
 7305            LanguageConfig {
 7306                brackets: BracketPairConfig {
 7307                    pairs: vec![
 7308                        BracketPair {
 7309                            start: "{".to_string(),
 7310                            end: "}".to_string(),
 7311                            close: true,
 7312                            newline: true,
 7313                        },
 7314                        BracketPair {
 7315                            start: "/* ".to_string(),
 7316                            end: " */".to_string(),
 7317                            close: true,
 7318                            newline: true,
 7319                        },
 7320                    ],
 7321                    ..Default::default()
 7322                },
 7323                ..Default::default()
 7324            },
 7325            Some(tree_sitter_rust::language()),
 7326        )
 7327        .with_indents_query("")
 7328        .unwrap(),
 7329    );
 7330
 7331    let text = concat!(
 7332        "{   }\n",     //
 7333        "  x\n",       //
 7334        "  /*   */\n", //
 7335        "x\n",         //
 7336        "{{} }\n",     //
 7337    );
 7338
 7339    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 7340    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 7341    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 7342    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 7343        .await;
 7344
 7345    _ = view.update(cx, |view, cx| {
 7346        view.change_selections(None, cx, |s| {
 7347            s.select_display_ranges([
 7348                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
 7349                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
 7350                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
 7351            ])
 7352        });
 7353        view.newline(&Newline, cx);
 7354
 7355        assert_eq!(
 7356            view.buffer().read(cx).read(cx).text(),
 7357            concat!(
 7358                "{ \n",    // Suppress rustfmt
 7359                "\n",      //
 7360                "}\n",     //
 7361                "  x\n",   //
 7362                "  /* \n", //
 7363                "  \n",    //
 7364                "  */\n",  //
 7365                "x\n",     //
 7366                "{{} \n",  //
 7367                "}\n",     //
 7368            )
 7369        );
 7370    });
 7371}
 7372
 7373#[gpui::test]
 7374fn test_highlighted_ranges(cx: &mut TestAppContext) {
 7375    init_test(cx, |_| {});
 7376
 7377    let editor = cx.add_window(|cx| {
 7378        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
 7379        build_editor(buffer.clone(), cx)
 7380    });
 7381
 7382    _ = editor.update(cx, |editor, cx| {
 7383        struct Type1;
 7384        struct Type2;
 7385
 7386        let buffer = editor.buffer.read(cx).snapshot(cx);
 7387
 7388        let anchor_range =
 7389            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
 7390
 7391        editor.highlight_background::<Type1>(
 7392            &[
 7393                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
 7394                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
 7395                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
 7396                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
 7397            ],
 7398            |_| Hsla::red(),
 7399            cx,
 7400        );
 7401        editor.highlight_background::<Type2>(
 7402            &[
 7403                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
 7404                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
 7405                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
 7406                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
 7407            ],
 7408            |_| Hsla::green(),
 7409            cx,
 7410        );
 7411
 7412        let snapshot = editor.snapshot(cx);
 7413        let mut highlighted_ranges = editor.background_highlights_in_range(
 7414            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
 7415            &snapshot,
 7416            cx.theme().colors(),
 7417        );
 7418        // Enforce a consistent ordering based on color without relying on the ordering of the
 7419        // highlight's `TypeId` which is non-executor.
 7420        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
 7421        assert_eq!(
 7422            highlighted_ranges,
 7423            &[
 7424                (
 7425                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
 7426                    Hsla::red(),
 7427                ),
 7428                (
 7429                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
 7430                    Hsla::red(),
 7431                ),
 7432                (
 7433                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
 7434                    Hsla::green(),
 7435                ),
 7436                (
 7437                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
 7438                    Hsla::green(),
 7439                ),
 7440            ]
 7441        );
 7442        assert_eq!(
 7443            editor.background_highlights_in_range(
 7444                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
 7445                &snapshot,
 7446                cx.theme().colors(),
 7447            ),
 7448            &[(
 7449                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
 7450                Hsla::red(),
 7451            )]
 7452        );
 7453    });
 7454}
 7455
 7456#[gpui::test]
 7457async fn test_following(cx: &mut gpui::TestAppContext) {
 7458    init_test(cx, |_| {});
 7459
 7460    let fs = FakeFs::new(cx.executor());
 7461    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 7462
 7463    let buffer = project.update(cx, |project, cx| {
 7464        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
 7465        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
 7466    });
 7467    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 7468    let follower = cx.update(|cx| {
 7469        cx.open_window(
 7470            WindowOptions {
 7471                bounds: Some(Bounds::from_corners(
 7472                    gpui::Point::new(0.into(), 0.into()),
 7473                    gpui::Point::new(10.into(), 80.into()),
 7474                )),
 7475                ..Default::default()
 7476            },
 7477            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
 7478        )
 7479    });
 7480
 7481    let is_still_following = Rc::new(RefCell::new(true));
 7482    let follower_edit_event_count = Rc::new(RefCell::new(0));
 7483    let pending_update = Rc::new(RefCell::new(None));
 7484    _ = follower.update(cx, {
 7485        let update = pending_update.clone();
 7486        let is_still_following = is_still_following.clone();
 7487        let follower_edit_event_count = follower_edit_event_count.clone();
 7488        |_, cx| {
 7489            cx.subscribe(
 7490                &leader.root_view(cx).unwrap(),
 7491                move |_, leader, event, cx| {
 7492                    leader
 7493                        .read(cx)
 7494                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 7495                },
 7496            )
 7497            .detach();
 7498
 7499            cx.subscribe(
 7500                &follower.root_view(cx).unwrap(),
 7501                move |_, _, event: &EditorEvent, _cx| {
 7502                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
 7503                        *is_still_following.borrow_mut() = false;
 7504                    }
 7505
 7506                    if let EditorEvent::BufferEdited = event {
 7507                        *follower_edit_event_count.borrow_mut() += 1;
 7508                    }
 7509                },
 7510            )
 7511            .detach();
 7512        }
 7513    });
 7514
 7515    // Update the selections only
 7516    _ = leader.update(cx, |leader, cx| {
 7517        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 7518    });
 7519    follower
 7520        .update(cx, |follower, cx| {
 7521            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7522        })
 7523        .unwrap()
 7524        .await
 7525        .unwrap();
 7526    _ = follower.update(cx, |follower, cx| {
 7527        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
 7528    });
 7529    assert_eq!(*is_still_following.borrow(), true);
 7530    assert_eq!(*follower_edit_event_count.borrow(), 0);
 7531
 7532    // Update the scroll position only
 7533    _ = leader.update(cx, |leader, cx| {
 7534        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 7535    });
 7536    follower
 7537        .update(cx, |follower, cx| {
 7538            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7539        })
 7540        .unwrap()
 7541        .await
 7542        .unwrap();
 7543    assert_eq!(
 7544        follower
 7545            .update(cx, |follower, cx| follower.scroll_position(cx))
 7546            .unwrap(),
 7547        gpui::Point::new(1.5, 3.5)
 7548    );
 7549    assert_eq!(*is_still_following.borrow(), true);
 7550    assert_eq!(*follower_edit_event_count.borrow(), 0);
 7551
 7552    // Update the selections and scroll position. The follower's scroll position is updated
 7553    // via autoscroll, not via the leader's exact scroll position.
 7554    _ = leader.update(cx, |leader, cx| {
 7555        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
 7556        leader.request_autoscroll(Autoscroll::newest(), cx);
 7557        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 7558    });
 7559    follower
 7560        .update(cx, |follower, cx| {
 7561            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7562        })
 7563        .unwrap()
 7564        .await
 7565        .unwrap();
 7566    _ = follower.update(cx, |follower, cx| {
 7567        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
 7568        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
 7569    });
 7570    assert_eq!(*is_still_following.borrow(), true);
 7571
 7572    // Creating a pending selection that precedes another selection
 7573    _ = leader.update(cx, |leader, cx| {
 7574        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 7575        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
 7576    });
 7577    follower
 7578        .update(cx, |follower, cx| {
 7579            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7580        })
 7581        .unwrap()
 7582        .await
 7583        .unwrap();
 7584    _ = follower.update(cx, |follower, cx| {
 7585        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
 7586    });
 7587    assert_eq!(*is_still_following.borrow(), true);
 7588
 7589    // Extend the pending selection so that it surrounds another selection
 7590    _ = leader.update(cx, |leader, cx| {
 7591        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
 7592    });
 7593    follower
 7594        .update(cx, |follower, cx| {
 7595            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7596        })
 7597        .unwrap()
 7598        .await
 7599        .unwrap();
 7600    _ = follower.update(cx, |follower, cx| {
 7601        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
 7602    });
 7603
 7604    // Scrolling locally breaks the follow
 7605    _ = follower.update(cx, |follower, cx| {
 7606        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
 7607        follower.set_scroll_anchor(
 7608            ScrollAnchor {
 7609                anchor: top_anchor,
 7610                offset: gpui::Point::new(0.0, 0.5),
 7611            },
 7612            cx,
 7613        );
 7614    });
 7615    assert_eq!(*is_still_following.borrow(), false);
 7616}
 7617
 7618#[gpui::test]
 7619async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
 7620    init_test(cx, |_| {});
 7621
 7622    let fs = FakeFs::new(cx.executor());
 7623    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 7624    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 7625    let pane = workspace
 7626        .update(cx, |workspace, _| workspace.active_pane().clone())
 7627        .unwrap();
 7628
 7629    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 7630
 7631    let leader = pane.update(cx, |_, cx| {
 7632        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
 7633        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
 7634    });
 7635
 7636    // Start following the editor when it has no excerpts.
 7637    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 7638    let follower_1 = cx
 7639        .update_window(*workspace.deref(), |_, cx| {
 7640            Editor::from_state_proto(
 7641                pane.clone(),
 7642                workspace.root_view(cx).unwrap(),
 7643                ViewId {
 7644                    creator: Default::default(),
 7645                    id: 0,
 7646                },
 7647                &mut state_message,
 7648                cx,
 7649            )
 7650        })
 7651        .unwrap()
 7652        .unwrap()
 7653        .await
 7654        .unwrap();
 7655
 7656    let update_message = Rc::new(RefCell::new(None));
 7657    follower_1.update(cx, {
 7658        let update = update_message.clone();
 7659        |_, cx| {
 7660            cx.subscribe(&leader, move |_, leader, event, cx| {
 7661                leader
 7662                    .read(cx)
 7663                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 7664            })
 7665            .detach();
 7666        }
 7667    });
 7668
 7669    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
 7670        (
 7671            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
 7672            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
 7673        )
 7674    });
 7675
 7676    // Insert some excerpts.
 7677    _ = leader.update(cx, |leader, cx| {
 7678        leader.buffer.update(cx, |multibuffer, cx| {
 7679            let excerpt_ids = multibuffer.push_excerpts(
 7680                buffer_1.clone(),
 7681                [
 7682                    ExcerptRange {
 7683                        context: 1..6,
 7684                        primary: None,
 7685                    },
 7686                    ExcerptRange {
 7687                        context: 12..15,
 7688                        primary: None,
 7689                    },
 7690                    ExcerptRange {
 7691                        context: 0..3,
 7692                        primary: None,
 7693                    },
 7694                ],
 7695                cx,
 7696            );
 7697            multibuffer.insert_excerpts_after(
 7698                excerpt_ids[0],
 7699                buffer_2.clone(),
 7700                [
 7701                    ExcerptRange {
 7702                        context: 8..12,
 7703                        primary: None,
 7704                    },
 7705                    ExcerptRange {
 7706                        context: 0..6,
 7707                        primary: None,
 7708                    },
 7709                ],
 7710                cx,
 7711            );
 7712        });
 7713    });
 7714
 7715    // Apply the update of adding the excerpts.
 7716    follower_1
 7717        .update(cx, |follower, cx| {
 7718            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 7719        })
 7720        .await
 7721        .unwrap();
 7722    assert_eq!(
 7723        follower_1.update(cx, |editor, cx| editor.text(cx)),
 7724        leader.update(cx, |editor, cx| editor.text(cx))
 7725    );
 7726    update_message.borrow_mut().take();
 7727
 7728    // Start following separately after it already has excerpts.
 7729    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 7730    let follower_2 = cx
 7731        .update_window(*workspace.deref(), |_, cx| {
 7732            Editor::from_state_proto(
 7733                pane.clone(),
 7734                workspace.root_view(cx).unwrap().clone(),
 7735                ViewId {
 7736                    creator: Default::default(),
 7737                    id: 0,
 7738                },
 7739                &mut state_message,
 7740                cx,
 7741            )
 7742        })
 7743        .unwrap()
 7744        .unwrap()
 7745        .await
 7746        .unwrap();
 7747    assert_eq!(
 7748        follower_2.update(cx, |editor, cx| editor.text(cx)),
 7749        leader.update(cx, |editor, cx| editor.text(cx))
 7750    );
 7751
 7752    // Remove some excerpts.
 7753    _ = leader.update(cx, |leader, cx| {
 7754        leader.buffer.update(cx, |multibuffer, cx| {
 7755            let excerpt_ids = multibuffer.excerpt_ids();
 7756            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
 7757            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
 7758        });
 7759    });
 7760
 7761    // Apply the update of removing the excerpts.
 7762    follower_1
 7763        .update(cx, |follower, cx| {
 7764            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 7765        })
 7766        .await
 7767        .unwrap();
 7768    follower_2
 7769        .update(cx, |follower, cx| {
 7770            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 7771        })
 7772        .await
 7773        .unwrap();
 7774    update_message.borrow_mut().take();
 7775    assert_eq!(
 7776        follower_1.update(cx, |editor, cx| editor.text(cx)),
 7777        leader.update(cx, |editor, cx| editor.text(cx))
 7778    );
 7779}
 7780
 7781#[gpui::test]
 7782async fn go_to_prev_overlapping_diagnostic(
 7783    executor: BackgroundExecutor,
 7784    cx: &mut gpui::TestAppContext,
 7785) {
 7786    init_test(cx, |_| {});
 7787
 7788    let mut cx = EditorTestContext::new(cx).await;
 7789    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
 7790
 7791    cx.set_state(indoc! {"
 7792        ˇfn func(abc def: i32) -> u32 {
 7793        }
 7794    "});
 7795
 7796    _ = cx.update(|cx| {
 7797        _ = project.update(cx, |project, cx| {
 7798            project
 7799                .update_diagnostics(
 7800                    LanguageServerId(0),
 7801                    lsp::PublishDiagnosticsParams {
 7802                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
 7803                        version: None,
 7804                        diagnostics: vec![
 7805                            lsp::Diagnostic {
 7806                                range: lsp::Range::new(
 7807                                    lsp::Position::new(0, 11),
 7808                                    lsp::Position::new(0, 12),
 7809                                ),
 7810                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 7811                                ..Default::default()
 7812                            },
 7813                            lsp::Diagnostic {
 7814                                range: lsp::Range::new(
 7815                                    lsp::Position::new(0, 12),
 7816                                    lsp::Position::new(0, 15),
 7817                                ),
 7818                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 7819                                ..Default::default()
 7820                            },
 7821                            lsp::Diagnostic {
 7822                                range: lsp::Range::new(
 7823                                    lsp::Position::new(0, 25),
 7824                                    lsp::Position::new(0, 28),
 7825                                ),
 7826                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 7827                                ..Default::default()
 7828                            },
 7829                        ],
 7830                    },
 7831                    &[],
 7832                    cx,
 7833                )
 7834                .unwrap()
 7835        });
 7836    });
 7837
 7838    executor.run_until_parked();
 7839
 7840    cx.update_editor(|editor, cx| {
 7841        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7842    });
 7843
 7844    cx.assert_editor_state(indoc! {"
 7845        fn func(abc def: i32) -> ˇu32 {
 7846        }
 7847    "});
 7848
 7849    cx.update_editor(|editor, cx| {
 7850        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7851    });
 7852
 7853    cx.assert_editor_state(indoc! {"
 7854        fn func(abc ˇdef: i32) -> u32 {
 7855        }
 7856    "});
 7857
 7858    cx.update_editor(|editor, cx| {
 7859        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7860    });
 7861
 7862    cx.assert_editor_state(indoc! {"
 7863        fn func(abcˇ def: i32) -> u32 {
 7864        }
 7865    "});
 7866
 7867    cx.update_editor(|editor, cx| {
 7868        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7869    });
 7870
 7871    cx.assert_editor_state(indoc! {"
 7872        fn func(abc def: i32) -> ˇu32 {
 7873        }
 7874    "});
 7875}
 7876
 7877#[gpui::test]
 7878async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 7879    init_test(cx, |_| {});
 7880
 7881    let mut cx = EditorTestContext::new(cx).await;
 7882
 7883    let diff_base = r#"
 7884        use some::mod;
 7885
 7886        const A: u32 = 42;
 7887
 7888        fn main() {
 7889            println!("hello");
 7890
 7891            println!("world");
 7892        }
 7893        "#
 7894    .unindent();
 7895
 7896    // Edits are modified, removed, modified, added
 7897    cx.set_state(
 7898        &r#"
 7899        use some::modified;
 7900
 7901        ˇ
 7902        fn main() {
 7903            println!("hello there");
 7904
 7905            println!("around the");
 7906            println!("world");
 7907        }
 7908        "#
 7909        .unindent(),
 7910    );
 7911
 7912    cx.set_diff_base(Some(&diff_base));
 7913    executor.run_until_parked();
 7914
 7915    cx.update_editor(|editor, cx| {
 7916        //Wrap around the bottom of the buffer
 7917        for _ in 0..3 {
 7918            editor.go_to_hunk(&GoToHunk, cx);
 7919        }
 7920    });
 7921
 7922    cx.assert_editor_state(
 7923        &r#"
 7924        ˇuse some::modified;
 7925
 7926
 7927        fn main() {
 7928            println!("hello there");
 7929
 7930            println!("around the");
 7931            println!("world");
 7932        }
 7933        "#
 7934        .unindent(),
 7935    );
 7936
 7937    cx.update_editor(|editor, cx| {
 7938        //Wrap around the top of the buffer
 7939        for _ in 0..2 {
 7940            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 7941        }
 7942    });
 7943
 7944    cx.assert_editor_state(
 7945        &r#"
 7946        use some::modified;
 7947
 7948
 7949        fn main() {
 7950        ˇ    println!("hello there");
 7951
 7952            println!("around the");
 7953            println!("world");
 7954        }
 7955        "#
 7956        .unindent(),
 7957    );
 7958
 7959    cx.update_editor(|editor, cx| {
 7960        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 7961    });
 7962
 7963    cx.assert_editor_state(
 7964        &r#"
 7965        use some::modified;
 7966
 7967        ˇ
 7968        fn main() {
 7969            println!("hello there");
 7970
 7971            println!("around the");
 7972            println!("world");
 7973        }
 7974        "#
 7975        .unindent(),
 7976    );
 7977
 7978    cx.update_editor(|editor, cx| {
 7979        for _ in 0..3 {
 7980            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 7981        }
 7982    });
 7983
 7984    cx.assert_editor_state(
 7985        &r#"
 7986        use some::modified;
 7987
 7988
 7989        fn main() {
 7990        ˇ    println!("hello there");
 7991
 7992            println!("around the");
 7993            println!("world");
 7994        }
 7995        "#
 7996        .unindent(),
 7997    );
 7998
 7999    cx.update_editor(|editor, cx| {
 8000        editor.fold(&Fold, cx);
 8001
 8002        //Make sure that the fold only gets one hunk
 8003        for _ in 0..4 {
 8004            editor.go_to_hunk(&GoToHunk, cx);
 8005        }
 8006    });
 8007
 8008    cx.assert_editor_state(
 8009        &r#"
 8010        ˇuse some::modified;
 8011
 8012
 8013        fn main() {
 8014            println!("hello there");
 8015
 8016            println!("around the");
 8017            println!("world");
 8018        }
 8019        "#
 8020        .unindent(),
 8021    );
 8022}
 8023
 8024#[test]
 8025fn test_split_words() {
 8026    fn split(text: &str) -> Vec<&str> {
 8027        split_words(text).collect()
 8028    }
 8029
 8030    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
 8031    assert_eq!(split("hello_world"), &["hello_", "world"]);
 8032    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
 8033    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
 8034    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
 8035    assert_eq!(split("helloworld"), &["helloworld"]);
 8036
 8037    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
 8038}
 8039
 8040#[gpui::test]
 8041async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
 8042    init_test(cx, |_| {});
 8043
 8044    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
 8045    let mut assert = |before, after| {
 8046        let _state_context = cx.set_state(before);
 8047        cx.update_editor(|editor, cx| {
 8048            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
 8049        });
 8050        cx.assert_editor_state(after);
 8051    };
 8052
 8053    // Outside bracket jumps to outside of matching bracket
 8054    assert("console.logˇ(var);", "console.log(var)ˇ;");
 8055    assert("console.log(var)ˇ;", "console.logˇ(var);");
 8056
 8057    // Inside bracket jumps to inside of matching bracket
 8058    assert("console.log(ˇvar);", "console.log(varˇ);");
 8059    assert("console.log(varˇ);", "console.log(ˇvar);");
 8060
 8061    // When outside a bracket and inside, favor jumping to the inside bracket
 8062    assert(
 8063        "console.log('foo', [1, 2, 3]ˇ);",
 8064        "console.log(ˇ'foo', [1, 2, 3]);",
 8065    );
 8066    assert(
 8067        "console.log(ˇ'foo', [1, 2, 3]);",
 8068        "console.log('foo', [1, 2, 3]ˇ);",
 8069    );
 8070
 8071    // Bias forward if two options are equally likely
 8072    assert(
 8073        "let result = curried_fun()ˇ();",
 8074        "let result = curried_fun()()ˇ;",
 8075    );
 8076
 8077    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
 8078    assert(
 8079        indoc! {"
 8080            function test() {
 8081                console.log('test')ˇ
 8082            }"},
 8083        indoc! {"
 8084            function test() {
 8085                console.logˇ('test')
 8086            }"},
 8087    );
 8088}
 8089
 8090#[gpui::test]
 8091async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
 8092    init_test(cx, |_| {});
 8093
 8094    let fs = FakeFs::new(cx.executor());
 8095    fs.insert_tree(
 8096        "/a",
 8097        json!({
 8098            "main.rs": "fn main() { let a = 5; }",
 8099            "other.rs": "// Test file",
 8100        }),
 8101    )
 8102    .await;
 8103    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8104
 8105    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8106    language_registry.add(Arc::new(Language::new(
 8107        LanguageConfig {
 8108            name: "Rust".into(),
 8109            matcher: LanguageMatcher {
 8110                path_suffixes: vec!["rs".to_string()],
 8111                ..Default::default()
 8112            },
 8113            brackets: BracketPairConfig {
 8114                pairs: vec![BracketPair {
 8115                    start: "{".to_string(),
 8116                    end: "}".to_string(),
 8117                    close: true,
 8118                    newline: true,
 8119                }],
 8120                disabled_scopes_by_bracket_ix: Vec::new(),
 8121            },
 8122            ..Default::default()
 8123        },
 8124        Some(tree_sitter_rust::language()),
 8125    )));
 8126    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8127        "Rust",
 8128        FakeLspAdapter {
 8129            capabilities: lsp::ServerCapabilities {
 8130                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
 8131                    first_trigger_character: "{".to_string(),
 8132                    more_trigger_character: None,
 8133                }),
 8134                ..Default::default()
 8135            },
 8136            ..Default::default()
 8137        },
 8138    );
 8139
 8140    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8141
 8142    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 8143
 8144    let worktree_id = workspace
 8145        .update(cx, |workspace, cx| {
 8146            workspace.project().update(cx, |project, cx| {
 8147                project.worktrees().next().unwrap().read(cx).id()
 8148            })
 8149        })
 8150        .unwrap();
 8151
 8152    let buffer = project
 8153        .update(cx, |project, cx| {
 8154            project.open_local_buffer("/a/main.rs", cx)
 8155        })
 8156        .await
 8157        .unwrap();
 8158    cx.executor().run_until_parked();
 8159    cx.executor().start_waiting();
 8160    let fake_server = fake_servers.next().await.unwrap();
 8161    let editor_handle = workspace
 8162        .update(cx, |workspace, cx| {
 8163            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 8164        })
 8165        .unwrap()
 8166        .await
 8167        .unwrap()
 8168        .downcast::<Editor>()
 8169        .unwrap();
 8170
 8171    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
 8172        assert_eq!(
 8173            params.text_document_position.text_document.uri,
 8174            lsp::Url::from_file_path("/a/main.rs").unwrap(),
 8175        );
 8176        assert_eq!(
 8177            params.text_document_position.position,
 8178            lsp::Position::new(0, 21),
 8179        );
 8180
 8181        Ok(Some(vec![lsp::TextEdit {
 8182            new_text: "]".to_string(),
 8183            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
 8184        }]))
 8185    });
 8186
 8187    editor_handle.update(cx, |editor, cx| {
 8188        editor.focus(cx);
 8189        editor.change_selections(None, cx, |s| {
 8190            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
 8191        });
 8192        editor.handle_input("{", cx);
 8193    });
 8194
 8195    cx.executor().run_until_parked();
 8196
 8197    _ = buffer.update(cx, |buffer, _| {
 8198        assert_eq!(
 8199            buffer.text(),
 8200            "fn main() { let a = {5}; }",
 8201            "No extra braces from on type formatting should appear in the buffer"
 8202        )
 8203    });
 8204}
 8205
 8206#[gpui::test]
 8207async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
 8208    init_test(cx, |_| {});
 8209
 8210    let fs = FakeFs::new(cx.executor());
 8211    fs.insert_tree(
 8212        "/a",
 8213        json!({
 8214            "main.rs": "fn main() { let a = 5; }",
 8215            "other.rs": "// Test file",
 8216        }),
 8217    )
 8218    .await;
 8219
 8220    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8221
 8222    let server_restarts = Arc::new(AtomicUsize::new(0));
 8223    let closure_restarts = Arc::clone(&server_restarts);
 8224    let language_server_name = "test language server";
 8225    let language_name: Arc<str> = "Rust".into();
 8226
 8227    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8228    language_registry.add(Arc::new(Language::new(
 8229        LanguageConfig {
 8230            name: Arc::clone(&language_name),
 8231            matcher: LanguageMatcher {
 8232                path_suffixes: vec!["rs".to_string()],
 8233                ..Default::default()
 8234            },
 8235            ..Default::default()
 8236        },
 8237        Some(tree_sitter_rust::language()),
 8238    )));
 8239    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8240        "Rust",
 8241        FakeLspAdapter {
 8242            name: language_server_name,
 8243            initialization_options: Some(json!({
 8244                "testOptionValue": true
 8245            })),
 8246            initializer: Some(Box::new(move |fake_server| {
 8247                let task_restarts = Arc::clone(&closure_restarts);
 8248                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
 8249                    task_restarts.fetch_add(1, atomic::Ordering::Release);
 8250                    futures::future::ready(Ok(()))
 8251                });
 8252            })),
 8253            ..Default::default()
 8254        },
 8255    );
 8256
 8257    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8258    let _buffer = project
 8259        .update(cx, |project, cx| {
 8260            project.open_local_buffer("/a/main.rs", cx)
 8261        })
 8262        .await
 8263        .unwrap();
 8264    let _fake_server = fake_servers.next().await.unwrap();
 8265    update_test_language_settings(cx, |language_settings| {
 8266        language_settings.languages.insert(
 8267            Arc::clone(&language_name),
 8268            LanguageSettingsContent {
 8269                tab_size: NonZeroU32::new(8),
 8270                ..Default::default()
 8271            },
 8272        );
 8273    });
 8274    cx.executor().run_until_parked();
 8275    assert_eq!(
 8276        server_restarts.load(atomic::Ordering::Acquire),
 8277        0,
 8278        "Should not restart LSP server on an unrelated change"
 8279    );
 8280
 8281    update_test_project_settings(cx, |project_settings| {
 8282        project_settings.lsp.insert(
 8283            "Some other server name".into(),
 8284            LspSettings {
 8285                binary: None,
 8286                settings: None,
 8287                initialization_options: Some(json!({
 8288                    "some other init value": false
 8289                })),
 8290            },
 8291        );
 8292    });
 8293    cx.executor().run_until_parked();
 8294    assert_eq!(
 8295        server_restarts.load(atomic::Ordering::Acquire),
 8296        0,
 8297        "Should not restart LSP server on an unrelated LSP settings change"
 8298    );
 8299
 8300    update_test_project_settings(cx, |project_settings| {
 8301        project_settings.lsp.insert(
 8302            language_server_name.into(),
 8303            LspSettings {
 8304                binary: None,
 8305                settings: None,
 8306                initialization_options: Some(json!({
 8307                    "anotherInitValue": false
 8308                })),
 8309            },
 8310        );
 8311    });
 8312    cx.executor().run_until_parked();
 8313    assert_eq!(
 8314        server_restarts.load(atomic::Ordering::Acquire),
 8315        1,
 8316        "Should restart LSP server on a related LSP settings change"
 8317    );
 8318
 8319    update_test_project_settings(cx, |project_settings| {
 8320        project_settings.lsp.insert(
 8321            language_server_name.into(),
 8322            LspSettings {
 8323                binary: None,
 8324                settings: None,
 8325                initialization_options: Some(json!({
 8326                    "anotherInitValue": false
 8327                })),
 8328            },
 8329        );
 8330    });
 8331    cx.executor().run_until_parked();
 8332    assert_eq!(
 8333        server_restarts.load(atomic::Ordering::Acquire),
 8334        1,
 8335        "Should not restart LSP server on a related LSP settings change that is the same"
 8336    );
 8337
 8338    update_test_project_settings(cx, |project_settings| {
 8339        project_settings.lsp.insert(
 8340            language_server_name.into(),
 8341            LspSettings {
 8342                binary: None,
 8343                settings: None,
 8344                initialization_options: None,
 8345            },
 8346        );
 8347    });
 8348    cx.executor().run_until_parked();
 8349    assert_eq!(
 8350        server_restarts.load(atomic::Ordering::Acquire),
 8351        2,
 8352        "Should restart LSP server on another related LSP settings change"
 8353    );
 8354}
 8355
 8356#[gpui::test]
 8357async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
 8358    init_test(cx, |_| {});
 8359
 8360    let mut cx = EditorLspTestContext::new_rust(
 8361        lsp::ServerCapabilities {
 8362            completion_provider: Some(lsp::CompletionOptions {
 8363                trigger_characters: Some(vec![".".to_string()]),
 8364                resolve_provider: Some(true),
 8365                ..Default::default()
 8366            }),
 8367            ..Default::default()
 8368        },
 8369        cx,
 8370    )
 8371    .await;
 8372
 8373    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
 8374    cx.simulate_keystroke(".");
 8375    let completion_item = lsp::CompletionItem {
 8376        label: "some".into(),
 8377        kind: Some(lsp::CompletionItemKind::SNIPPET),
 8378        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
 8379        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
 8380            kind: lsp::MarkupKind::Markdown,
 8381            value: "```rust\nSome(2)\n```".to_string(),
 8382        })),
 8383        deprecated: Some(false),
 8384        sort_text: Some("fffffff2".to_string()),
 8385        filter_text: Some("some".to_string()),
 8386        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 8387        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 8388            range: lsp::Range {
 8389                start: lsp::Position {
 8390                    line: 0,
 8391                    character: 22,
 8392                },
 8393                end: lsp::Position {
 8394                    line: 0,
 8395                    character: 22,
 8396                },
 8397            },
 8398            new_text: "Some(2)".to_string(),
 8399        })),
 8400        additional_text_edits: Some(vec![lsp::TextEdit {
 8401            range: lsp::Range {
 8402                start: lsp::Position {
 8403                    line: 0,
 8404                    character: 20,
 8405                },
 8406                end: lsp::Position {
 8407                    line: 0,
 8408                    character: 22,
 8409                },
 8410            },
 8411            new_text: "".to_string(),
 8412        }]),
 8413        ..Default::default()
 8414    };
 8415
 8416    let closure_completion_item = completion_item.clone();
 8417    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
 8418        let task_completion_item = closure_completion_item.clone();
 8419        async move {
 8420            Ok(Some(lsp::CompletionResponse::Array(vec![
 8421                task_completion_item,
 8422            ])))
 8423        }
 8424    });
 8425
 8426    request.next().await;
 8427
 8428    cx.condition(|editor, _| editor.context_menu_visible())
 8429        .await;
 8430    let apply_additional_edits = cx.update_editor(|editor, cx| {
 8431        editor
 8432            .confirm_completion(&ConfirmCompletion::default(), cx)
 8433            .unwrap()
 8434    });
 8435    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
 8436
 8437    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
 8438        let task_completion_item = completion_item.clone();
 8439        async move { Ok(task_completion_item) }
 8440    })
 8441    .next()
 8442    .await
 8443    .unwrap();
 8444    apply_additional_edits.await.unwrap();
 8445    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
 8446}
 8447
 8448#[gpui::test]
 8449async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
 8450    init_test(cx, |_| {});
 8451
 8452    let mut cx = EditorLspTestContext::new(
 8453        Language::new(
 8454            LanguageConfig {
 8455                matcher: LanguageMatcher {
 8456                    path_suffixes: vec!["jsx".into()],
 8457                    ..Default::default()
 8458                },
 8459                overrides: [(
 8460                    "element".into(),
 8461                    LanguageConfigOverride {
 8462                        word_characters: Override::Set(['-'].into_iter().collect()),
 8463                        ..Default::default()
 8464                    },
 8465                )]
 8466                .into_iter()
 8467                .collect(),
 8468                ..Default::default()
 8469            },
 8470            Some(tree_sitter_typescript::language_tsx()),
 8471        )
 8472        .with_override_query("(jsx_self_closing_element) @element")
 8473        .unwrap(),
 8474        lsp::ServerCapabilities {
 8475            completion_provider: Some(lsp::CompletionOptions {
 8476                trigger_characters: Some(vec![":".to_string()]),
 8477                ..Default::default()
 8478            }),
 8479            ..Default::default()
 8480        },
 8481        cx,
 8482    )
 8483    .await;
 8484
 8485    cx.lsp
 8486        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 8487            Ok(Some(lsp::CompletionResponse::Array(vec![
 8488                lsp::CompletionItem {
 8489                    label: "bg-blue".into(),
 8490                    ..Default::default()
 8491                },
 8492                lsp::CompletionItem {
 8493                    label: "bg-red".into(),
 8494                    ..Default::default()
 8495                },
 8496                lsp::CompletionItem {
 8497                    label: "bg-yellow".into(),
 8498                    ..Default::default()
 8499                },
 8500            ])))
 8501        });
 8502
 8503    cx.set_state(r#"<p class="bgˇ" />"#);
 8504
 8505    // Trigger completion when typing a dash, because the dash is an extra
 8506    // word character in the 'element' scope, which contains the cursor.
 8507    cx.simulate_keystroke("-");
 8508    cx.executor().run_until_parked();
 8509    cx.update_editor(|editor, _| {
 8510        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8511            assert_eq!(
 8512                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8513                &["bg-red", "bg-blue", "bg-yellow"]
 8514            );
 8515        } else {
 8516            panic!("expected completion menu to be open");
 8517        }
 8518    });
 8519
 8520    cx.simulate_keystroke("l");
 8521    cx.executor().run_until_parked();
 8522    cx.update_editor(|editor, _| {
 8523        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8524            assert_eq!(
 8525                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8526                &["bg-blue", "bg-yellow"]
 8527            );
 8528        } else {
 8529            panic!("expected completion menu to be open");
 8530        }
 8531    });
 8532
 8533    // When filtering completions, consider the character after the '-' to
 8534    // be the start of a subword.
 8535    cx.set_state(r#"<p class="yelˇ" />"#);
 8536    cx.simulate_keystroke("l");
 8537    cx.executor().run_until_parked();
 8538    cx.update_editor(|editor, _| {
 8539        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8540            assert_eq!(
 8541                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8542                &["bg-yellow"]
 8543            );
 8544        } else {
 8545            panic!("expected completion menu to be open");
 8546        }
 8547    });
 8548}
 8549
 8550#[gpui::test]
 8551async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
 8552    init_test(cx, |settings| {
 8553        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
 8554    });
 8555
 8556    let fs = FakeFs::new(cx.executor());
 8557    fs.insert_file("/file.rs", Default::default()).await;
 8558
 8559    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 8560    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8561
 8562    language_registry.add(Arc::new(Language::new(
 8563        LanguageConfig {
 8564            name: "Rust".into(),
 8565            matcher: LanguageMatcher {
 8566                path_suffixes: vec!["rs".to_string()],
 8567                ..Default::default()
 8568            },
 8569            prettier_parser_name: Some("test_parser".to_string()),
 8570            ..Default::default()
 8571        },
 8572        Some(tree_sitter_rust::language()),
 8573    )));
 8574
 8575    let test_plugin = "test_plugin";
 8576    let _ = language_registry.register_fake_lsp_adapter(
 8577        "Rust",
 8578        FakeLspAdapter {
 8579            prettier_plugins: vec![test_plugin],
 8580            ..Default::default()
 8581        },
 8582    );
 8583
 8584    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
 8585    let buffer = project
 8586        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 8587        .await
 8588        .unwrap();
 8589
 8590    let buffer_text = "one\ntwo\nthree\n";
 8591    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 8592    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 8593    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
 8594
 8595    editor
 8596        .update(cx, |editor, cx| {
 8597            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 8598        })
 8599        .unwrap()
 8600        .await;
 8601    assert_eq!(
 8602        editor.update(cx, |editor, cx| editor.text(cx)),
 8603        buffer_text.to_string() + prettier_format_suffix,
 8604        "Test prettier formatting was not applied to the original buffer text",
 8605    );
 8606
 8607    update_test_language_settings(cx, |settings| {
 8608        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 8609    });
 8610    let format = editor.update(cx, |editor, cx| {
 8611        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 8612    });
 8613    format.await.unwrap();
 8614    assert_eq!(
 8615        editor.update(cx, |editor, cx| editor.text(cx)),
 8616        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
 8617        "Autoformatting (via test prettier) was not applied to the original buffer text",
 8618    );
 8619}
 8620
 8621#[gpui::test]
 8622async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
 8623    init_test(cx, |_| {});
 8624    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 8625    let base_text = indoc! {r#"struct Row;
 8626struct Row1;
 8627struct Row2;
 8628
 8629struct Row4;
 8630struct Row5;
 8631struct Row6;
 8632
 8633struct Row8;
 8634struct Row9;
 8635struct Row10;"#};
 8636
 8637    // When addition hunks are not adjacent to carets, no hunk revert is performed
 8638    assert_hunk_revert(
 8639        indoc! {r#"struct Row;
 8640                   struct Row1;
 8641                   struct Row1.1;
 8642                   struct Row1.2;
 8643                   struct Row2;ˇ
 8644
 8645                   struct Row4;
 8646                   struct Row5;
 8647                   struct Row6;
 8648
 8649                   struct Row8;
 8650                   ˇstruct Row9;
 8651                   struct Row9.1;
 8652                   struct Row9.2;
 8653                   struct Row9.3;
 8654                   struct Row10;"#},
 8655        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 8656        indoc! {r#"struct Row;
 8657                   struct Row1;
 8658                   struct Row1.1;
 8659                   struct Row1.2;
 8660                   struct Row2;ˇ
 8661
 8662                   struct Row4;
 8663                   struct Row5;
 8664                   struct Row6;
 8665
 8666                   struct Row8;
 8667                   ˇstruct Row9;
 8668                   struct Row9.1;
 8669                   struct Row9.2;
 8670                   struct Row9.3;
 8671                   struct Row10;"#},
 8672        base_text,
 8673        &mut cx,
 8674    );
 8675    // Same for selections
 8676    assert_hunk_revert(
 8677        indoc! {r#"struct Row;
 8678                   struct Row1;
 8679                   struct Row2;
 8680                   struct Row2.1;
 8681                   struct Row2.2;
 8682                   «ˇ
 8683                   struct Row4;
 8684                   struct» Row5;
 8685                   «struct Row6;
 8686                   ˇ»
 8687                   struct Row9.1;
 8688                   struct Row9.2;
 8689                   struct Row9.3;
 8690                   struct Row8;
 8691                   struct Row9;
 8692                   struct Row10;"#},
 8693        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 8694        indoc! {r#"struct Row;
 8695                   struct Row1;
 8696                   struct Row2;
 8697                   struct Row2.1;
 8698                   struct Row2.2;
 8699                   «ˇ
 8700                   struct Row4;
 8701                   struct» Row5;
 8702                   «struct Row6;
 8703                   ˇ»
 8704                   struct Row9.1;
 8705                   struct Row9.2;
 8706                   struct Row9.3;
 8707                   struct Row8;
 8708                   struct Row9;
 8709                   struct Row10;"#},
 8710        base_text,
 8711        &mut cx,
 8712    );
 8713
 8714    // When carets and selections intersect the addition hunks, those are reverted.
 8715    // Adjacent carets got merged.
 8716    assert_hunk_revert(
 8717        indoc! {r#"struct Row;
 8718                   ˇ// something on the top
 8719                   struct Row1;
 8720                   struct Row2;
 8721                   struct Roˇw3.1;
 8722                   struct Row2.2;
 8723                   struct Row2.3;ˇ
 8724
 8725                   struct Row4;
 8726                   struct ˇRow5.1;
 8727                   struct Row5.2;
 8728                   struct «Rowˇ»5.3;
 8729                   struct Row5;
 8730                   struct Row6;
 8731                   ˇ
 8732                   struct Row9.1;
 8733                   struct «Rowˇ»9.2;
 8734                   struct «ˇRow»9.3;
 8735                   struct Row8;
 8736                   struct Row9;
 8737                   «ˇ// something on bottom»
 8738                   struct Row10;"#},
 8739        vec![
 8740            DiffHunkStatus::Added,
 8741            DiffHunkStatus::Added,
 8742            DiffHunkStatus::Added,
 8743            DiffHunkStatus::Added,
 8744            DiffHunkStatus::Added,
 8745        ],
 8746        indoc! {r#"struct Row;
 8747                   ˇstruct Row1;
 8748                   struct Row2;
 8749                   ˇ
 8750                   struct Row4;
 8751                   ˇstruct Row5;
 8752                   struct Row6;
 8753                   ˇ
 8754                   ˇstruct Row8;
 8755                   struct Row9;
 8756                   ˇstruct Row10;"#},
 8757        base_text,
 8758        &mut cx,
 8759    );
 8760}
 8761
 8762#[gpui::test]
 8763async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
 8764    init_test(cx, |_| {});
 8765    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 8766    let base_text = indoc! {r#"struct Row;
 8767struct Row1;
 8768struct Row2;
 8769
 8770struct Row4;
 8771struct Row5;
 8772struct Row6;
 8773
 8774struct Row8;
 8775struct Row9;
 8776struct Row10;"#};
 8777
 8778    // Modification hunks behave the same as the addition ones.
 8779    assert_hunk_revert(
 8780        indoc! {r#"struct Row;
 8781                   struct Row1;
 8782                   struct Row33;
 8783                   ˇ
 8784                   struct Row4;
 8785                   struct Row5;
 8786                   struct Row6;
 8787                   ˇ
 8788                   struct Row99;
 8789                   struct Row9;
 8790                   struct Row10;"#},
 8791        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 8792        indoc! {r#"struct Row;
 8793                   struct Row1;
 8794                   struct Row33;
 8795                   ˇ
 8796                   struct Row4;
 8797                   struct Row5;
 8798                   struct Row6;
 8799                   ˇ
 8800                   struct Row99;
 8801                   struct Row9;
 8802                   struct Row10;"#},
 8803        base_text,
 8804        &mut cx,
 8805    );
 8806    assert_hunk_revert(
 8807        indoc! {r#"struct Row;
 8808                   struct Row1;
 8809                   struct Row33;
 8810                   «ˇ
 8811                   struct Row4;
 8812                   struct» Row5;
 8813                   «struct Row6;
 8814                   ˇ»
 8815                   struct Row99;
 8816                   struct Row9;
 8817                   struct Row10;"#},
 8818        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 8819        indoc! {r#"struct Row;
 8820                   struct Row1;
 8821                   struct Row33;
 8822                   «ˇ
 8823                   struct Row4;
 8824                   struct» Row5;
 8825                   «struct Row6;
 8826                   ˇ»
 8827                   struct Row99;
 8828                   struct Row9;
 8829                   struct Row10;"#},
 8830        base_text,
 8831        &mut cx,
 8832    );
 8833
 8834    assert_hunk_revert(
 8835        indoc! {r#"ˇstruct Row1.1;
 8836                   struct Row1;
 8837                   «ˇstr»uct Row22;
 8838
 8839                   struct ˇRow44;
 8840                   struct Row5;
 8841                   struct «Rˇ»ow66;ˇ
 8842
 8843                   «struˇ»ct Row88;
 8844                   struct Row9;
 8845                   struct Row1011;ˇ"#},
 8846        vec![
 8847            DiffHunkStatus::Modified,
 8848            DiffHunkStatus::Modified,
 8849            DiffHunkStatus::Modified,
 8850            DiffHunkStatus::Modified,
 8851            DiffHunkStatus::Modified,
 8852            DiffHunkStatus::Modified,
 8853        ],
 8854        indoc! {r#"struct Row;
 8855                   ˇstruct Row1;
 8856                   struct Row2;
 8857                   ˇ
 8858                   struct Row4;
 8859                   ˇstruct Row5;
 8860                   struct Row6;
 8861                   ˇ
 8862                   struct Row8;
 8863                   ˇstruct Row9;
 8864                   struct Row10;ˇ"#},
 8865        base_text,
 8866        &mut cx,
 8867    );
 8868}
 8869
 8870#[gpui::test]
 8871async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
 8872    init_test(cx, |_| {});
 8873    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 8874    let base_text = indoc! {r#"struct Row;
 8875struct Row1;
 8876struct Row2;
 8877
 8878struct Row4;
 8879struct Row5;
 8880struct Row6;
 8881
 8882struct Row8;
 8883struct Row9;
 8884struct Row10;"#};
 8885
 8886    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
 8887    assert_hunk_revert(
 8888        indoc! {r#"struct Row;
 8889                   struct Row2;
 8890
 8891                   ˇstruct Row4;
 8892                   struct Row5;
 8893                   struct Row6;
 8894                   ˇ
 8895                   struct Row8;
 8896                   struct Row10;"#},
 8897        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 8898        indoc! {r#"struct Row;
 8899                   struct Row2;
 8900
 8901                   ˇstruct Row4;
 8902                   struct Row5;
 8903                   struct Row6;
 8904                   ˇ
 8905                   struct Row8;
 8906                   struct Row10;"#},
 8907        base_text,
 8908        &mut cx,
 8909    );
 8910    assert_hunk_revert(
 8911        indoc! {r#"struct Row;
 8912                   struct Row2;
 8913
 8914                   «ˇstruct Row4;
 8915                   struct» Row5;
 8916                   «struct Row6;
 8917                   ˇ»
 8918                   struct Row8;
 8919                   struct Row10;"#},
 8920        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 8921        indoc! {r#"struct Row;
 8922                   struct Row2;
 8923
 8924                   «ˇstruct Row4;
 8925                   struct» Row5;
 8926                   «struct Row6;
 8927                   ˇ»
 8928                   struct Row8;
 8929                   struct Row10;"#},
 8930        base_text,
 8931        &mut cx,
 8932    );
 8933
 8934    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
 8935    assert_hunk_revert(
 8936        indoc! {r#"struct Row;
 8937                   ˇstruct Row2;
 8938
 8939                   struct Row4;
 8940                   struct Row5;
 8941                   struct Row6;
 8942
 8943                   struct Row8;ˇ
 8944                   struct Row10;"#},
 8945        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 8946        indoc! {r#"struct Row;
 8947                   struct Row1;
 8948                   ˇstruct Row2;
 8949
 8950                   struct Row4;
 8951                   struct Row5;
 8952                   struct Row6;
 8953
 8954                   struct Row8;ˇ
 8955                   struct Row9;
 8956                   struct Row10;"#},
 8957        base_text,
 8958        &mut cx,
 8959    );
 8960    assert_hunk_revert(
 8961        indoc! {r#"struct Row;
 8962                   struct Row2«ˇ;
 8963                   struct Row4;
 8964                   struct» Row5;
 8965                   «struct Row6;
 8966
 8967                   struct Row8;ˇ»
 8968                   struct Row10;"#},
 8969        vec![
 8970            DiffHunkStatus::Removed,
 8971            DiffHunkStatus::Removed,
 8972            DiffHunkStatus::Removed,
 8973        ],
 8974        indoc! {r#"struct Row;
 8975                   struct Row1;
 8976                   struct Row2«ˇ;
 8977
 8978                   struct Row4;
 8979                   struct» Row5;
 8980                   «struct Row6;
 8981
 8982                   struct Row8;ˇ»
 8983                   struct Row9;
 8984                   struct Row10;"#},
 8985        base_text,
 8986        &mut cx,
 8987    );
 8988}
 8989
 8990#[gpui::test]
 8991async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
 8992    init_test(cx, |_| {});
 8993
 8994    let cols = 4;
 8995    let rows = 10;
 8996    let sample_text_1 = sample_text(rows, cols, 'a');
 8997    assert_eq!(
 8998        sample_text_1,
 8999        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9000    );
 9001    let sample_text_2 = sample_text(rows, cols, 'l');
 9002    assert_eq!(
 9003        sample_text_2,
 9004        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9005    );
 9006    let sample_text_3 = sample_text(rows, cols, 'v');
 9007    assert_eq!(
 9008        sample_text_3,
 9009        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9010    );
 9011
 9012    fn diff_every_buffer_row(
 9013        buffer: &Model<Buffer>,
 9014        sample_text: String,
 9015        cols: usize,
 9016        cx: &mut gpui::TestAppContext,
 9017    ) {
 9018        // revert first character in each row, creating one large diff hunk per buffer
 9019        let is_first_char = |offset: usize| offset % cols == 0;
 9020        buffer.update(cx, |buffer, cx| {
 9021            buffer.set_text(
 9022                sample_text
 9023                    .chars()
 9024                    .enumerate()
 9025                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
 9026                    .collect::<String>(),
 9027                cx,
 9028            );
 9029            buffer.set_diff_base(Some(sample_text.into()), cx);
 9030        });
 9031        cx.executor().run_until_parked();
 9032    }
 9033
 9034    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9035    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9036
 9037    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9038    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9039
 9040    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9041    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9042
 9043    let multibuffer = cx.new_model(|cx| {
 9044        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9045        multibuffer.push_excerpts(
 9046            buffer_1.clone(),
 9047            [
 9048                ExcerptRange {
 9049                    context: Point::new(0, 0)..Point::new(3, 0),
 9050                    primary: None,
 9051                },
 9052                ExcerptRange {
 9053                    context: Point::new(5, 0)..Point::new(7, 0),
 9054                    primary: None,
 9055                },
 9056                ExcerptRange {
 9057                    context: Point::new(9, 0)..Point::new(10, 4),
 9058                    primary: None,
 9059                },
 9060            ],
 9061            cx,
 9062        );
 9063        multibuffer.push_excerpts(
 9064            buffer_2.clone(),
 9065            [
 9066                ExcerptRange {
 9067                    context: Point::new(0, 0)..Point::new(3, 0),
 9068                    primary: None,
 9069                },
 9070                ExcerptRange {
 9071                    context: Point::new(5, 0)..Point::new(7, 0),
 9072                    primary: None,
 9073                },
 9074                ExcerptRange {
 9075                    context: Point::new(9, 0)..Point::new(10, 4),
 9076                    primary: None,
 9077                },
 9078            ],
 9079            cx,
 9080        );
 9081        multibuffer.push_excerpts(
 9082            buffer_3.clone(),
 9083            [
 9084                ExcerptRange {
 9085                    context: Point::new(0, 0)..Point::new(3, 0),
 9086                    primary: None,
 9087                },
 9088                ExcerptRange {
 9089                    context: Point::new(5, 0)..Point::new(7, 0),
 9090                    primary: None,
 9091                },
 9092                ExcerptRange {
 9093                    context: Point::new(9, 0)..Point::new(10, 4),
 9094                    primary: None,
 9095                },
 9096            ],
 9097            cx,
 9098        );
 9099        multibuffer
 9100    });
 9101
 9102    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 9103    editor.update(cx, |editor, cx| {
 9104        assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
 9105        editor.select_all(&SelectAll, cx);
 9106        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9107    });
 9108    cx.executor().run_until_parked();
 9109    // When all ranges are selected, all buffer hunks are reverted.
 9110    editor.update(cx, |editor, cx| {
 9111        assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
 9112    });
 9113    buffer_1.update(cx, |buffer, _| {
 9114        assert_eq!(buffer.text(), sample_text_1);
 9115    });
 9116    buffer_2.update(cx, |buffer, _| {
 9117        assert_eq!(buffer.text(), sample_text_2);
 9118    });
 9119    buffer_3.update(cx, |buffer, _| {
 9120        assert_eq!(buffer.text(), sample_text_3);
 9121    });
 9122
 9123    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9124    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9125    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9126    editor.update(cx, |editor, cx| {
 9127        editor.change_selections(None, cx, |s| {
 9128            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
 9129        });
 9130        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9131    });
 9132    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
 9133    // but not affect buffer_2 and its related excerpts.
 9134    editor.update(cx, |editor, cx| {
 9135        assert_eq!(
 9136            editor.text(cx),
 9137            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
 9138        );
 9139    });
 9140    buffer_1.update(cx, |buffer, _| {
 9141        assert_eq!(buffer.text(), sample_text_1);
 9142    });
 9143    buffer_2.update(cx, |buffer, _| {
 9144        assert_eq!(
 9145            buffer.text(),
 9146            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
 9147        );
 9148    });
 9149    buffer_3.update(cx, |buffer, _| {
 9150        assert_eq!(
 9151            buffer.text(),
 9152            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
 9153        );
 9154    });
 9155}
 9156
 9157#[gpui::test]
 9158async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
 9159    init_test(cx, |_| {});
 9160
 9161    let cols = 4;
 9162    let rows = 10;
 9163    let sample_text_1 = sample_text(rows, cols, 'a');
 9164    assert_eq!(
 9165        sample_text_1,
 9166        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9167    );
 9168    let sample_text_2 = sample_text(rows, cols, 'l');
 9169    assert_eq!(
 9170        sample_text_2,
 9171        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9172    );
 9173    let sample_text_3 = sample_text(rows, cols, 'v');
 9174    assert_eq!(
 9175        sample_text_3,
 9176        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9177    );
 9178
 9179    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9180    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9181    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9182
 9183    let multi_buffer = cx.new_model(|cx| {
 9184        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9185        multibuffer.push_excerpts(
 9186            buffer_1.clone(),
 9187            [
 9188                ExcerptRange {
 9189                    context: Point::new(0, 0)..Point::new(3, 0),
 9190                    primary: None,
 9191                },
 9192                ExcerptRange {
 9193                    context: Point::new(5, 0)..Point::new(7, 0),
 9194                    primary: None,
 9195                },
 9196                ExcerptRange {
 9197                    context: Point::new(9, 0)..Point::new(10, 4),
 9198                    primary: None,
 9199                },
 9200            ],
 9201            cx,
 9202        );
 9203        multibuffer.push_excerpts(
 9204            buffer_2.clone(),
 9205            [
 9206                ExcerptRange {
 9207                    context: Point::new(0, 0)..Point::new(3, 0),
 9208                    primary: None,
 9209                },
 9210                ExcerptRange {
 9211                    context: Point::new(5, 0)..Point::new(7, 0),
 9212                    primary: None,
 9213                },
 9214                ExcerptRange {
 9215                    context: Point::new(9, 0)..Point::new(10, 4),
 9216                    primary: None,
 9217                },
 9218            ],
 9219            cx,
 9220        );
 9221        multibuffer.push_excerpts(
 9222            buffer_3.clone(),
 9223            [
 9224                ExcerptRange {
 9225                    context: Point::new(0, 0)..Point::new(3, 0),
 9226                    primary: None,
 9227                },
 9228                ExcerptRange {
 9229                    context: Point::new(5, 0)..Point::new(7, 0),
 9230                    primary: None,
 9231                },
 9232                ExcerptRange {
 9233                    context: Point::new(9, 0)..Point::new(10, 4),
 9234                    primary: None,
 9235                },
 9236            ],
 9237            cx,
 9238        );
 9239        multibuffer
 9240    });
 9241
 9242    let fs = FakeFs::new(cx.executor());
 9243    fs.insert_tree(
 9244        "/a",
 9245        json!({
 9246            "main.rs": sample_text_1,
 9247            "other.rs": sample_text_2,
 9248            "lib.rs": sample_text_3,
 9249        }),
 9250    )
 9251    .await;
 9252    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 9253    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 9254    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9255    let multi_buffer_editor =
 9256        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
 9257    let multibuffer_item_id = workspace
 9258        .update(cx, |workspace, cx| {
 9259            assert!(
 9260                workspace.active_item(cx).is_none(),
 9261                "active item should be None before the first item is added"
 9262            );
 9263            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
 9264            let active_item = workspace
 9265                .active_item(cx)
 9266                .expect("should have an active item after adding the multi buffer");
 9267            assert!(
 9268                !active_item.is_singleton(cx),
 9269                "A multi buffer was expected to active after adding"
 9270            );
 9271            active_item.item_id()
 9272        })
 9273        .unwrap();
 9274    cx.executor().run_until_parked();
 9275
 9276    multi_buffer_editor.update(cx, |editor, cx| {
 9277        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 9278        editor.open_excerpts(&OpenExcerpts, cx);
 9279    });
 9280    cx.executor().run_until_parked();
 9281    let first_item_id = workspace
 9282        .update(cx, |workspace, cx| {
 9283            let active_item = workspace
 9284                .active_item(cx)
 9285                .expect("should have an active item after navigating into the 1st buffer");
 9286            let first_item_id = active_item.item_id();
 9287            assert_ne!(
 9288                first_item_id, multibuffer_item_id,
 9289                "Should navigate into the 1st buffer and activate it"
 9290            );
 9291            assert!(
 9292                active_item.is_singleton(cx),
 9293                "New active item should be a singleton buffer"
 9294            );
 9295            assert_eq!(
 9296                active_item
 9297                    .act_as::<Editor>(cx)
 9298                    .expect("should have navigated into an editor for the 1st buffer")
 9299                    .read(cx)
 9300                    .text(cx),
 9301                sample_text_1
 9302            );
 9303
 9304            workspace
 9305                .go_back(workspace.active_pane().downgrade(), cx)
 9306                .detach_and_log_err(cx);
 9307
 9308            first_item_id
 9309        })
 9310        .unwrap();
 9311    cx.executor().run_until_parked();
 9312    workspace
 9313        .update(cx, |workspace, cx| {
 9314            let active_item = workspace
 9315                .active_item(cx)
 9316                .expect("should have an active item after navigating back");
 9317            assert_eq!(
 9318                active_item.item_id(),
 9319                multibuffer_item_id,
 9320                "Should navigate back to the multi buffer"
 9321            );
 9322            assert!(!active_item.is_singleton(cx));
 9323        })
 9324        .unwrap();
 9325
 9326    multi_buffer_editor.update(cx, |editor, cx| {
 9327        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9328            s.select_ranges(Some(39..40))
 9329        });
 9330        editor.open_excerpts(&OpenExcerpts, cx);
 9331    });
 9332    cx.executor().run_until_parked();
 9333    let second_item_id = workspace
 9334        .update(cx, |workspace, cx| {
 9335            let active_item = workspace
 9336                .active_item(cx)
 9337                .expect("should have an active item after navigating into the 2nd buffer");
 9338            let second_item_id = active_item.item_id();
 9339            assert_ne!(
 9340                second_item_id, multibuffer_item_id,
 9341                "Should navigate away from the multibuffer"
 9342            );
 9343            assert_ne!(
 9344                second_item_id, first_item_id,
 9345                "Should navigate into the 2nd buffer and activate it"
 9346            );
 9347            assert!(
 9348                active_item.is_singleton(cx),
 9349                "New active item should be a singleton buffer"
 9350            );
 9351            assert_eq!(
 9352                active_item
 9353                    .act_as::<Editor>(cx)
 9354                    .expect("should have navigated into an editor")
 9355                    .read(cx)
 9356                    .text(cx),
 9357                sample_text_2
 9358            );
 9359
 9360            workspace
 9361                .go_back(workspace.active_pane().downgrade(), cx)
 9362                .detach_and_log_err(cx);
 9363
 9364            second_item_id
 9365        })
 9366        .unwrap();
 9367    cx.executor().run_until_parked();
 9368    workspace
 9369        .update(cx, |workspace, cx| {
 9370            let active_item = workspace
 9371                .active_item(cx)
 9372                .expect("should have an active item after navigating back from the 2nd buffer");
 9373            assert_eq!(
 9374                active_item.item_id(),
 9375                multibuffer_item_id,
 9376                "Should navigate back from the 2nd buffer to the multi buffer"
 9377            );
 9378            assert!(!active_item.is_singleton(cx));
 9379        })
 9380        .unwrap();
 9381
 9382    multi_buffer_editor.update(cx, |editor, cx| {
 9383        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9384            s.select_ranges(Some(60..70))
 9385        });
 9386        editor.open_excerpts(&OpenExcerpts, cx);
 9387    });
 9388    cx.executor().run_until_parked();
 9389    workspace
 9390        .update(cx, |workspace, cx| {
 9391            let active_item = workspace
 9392                .active_item(cx)
 9393                .expect("should have an active item after navigating into the 3rd buffer");
 9394            let third_item_id = active_item.item_id();
 9395            assert_ne!(
 9396                third_item_id, multibuffer_item_id,
 9397                "Should navigate into the 3rd buffer and activate it"
 9398            );
 9399            assert_ne!(third_item_id, first_item_id);
 9400            assert_ne!(third_item_id, second_item_id);
 9401            assert!(
 9402                active_item.is_singleton(cx),
 9403                "New active item should be a singleton buffer"
 9404            );
 9405            assert_eq!(
 9406                active_item
 9407                    .act_as::<Editor>(cx)
 9408                    .expect("should have navigated into an editor")
 9409                    .read(cx)
 9410                    .text(cx),
 9411                sample_text_3
 9412            );
 9413
 9414            workspace
 9415                .go_back(workspace.active_pane().downgrade(), cx)
 9416                .detach_and_log_err(cx);
 9417        })
 9418        .unwrap();
 9419    cx.executor().run_until_parked();
 9420    workspace
 9421        .update(cx, |workspace, cx| {
 9422            let active_item = workspace
 9423                .active_item(cx)
 9424                .expect("should have an active item after navigating back from the 3rd buffer");
 9425            assert_eq!(
 9426                active_item.item_id(),
 9427                multibuffer_item_id,
 9428                "Should navigate back from the 3rd buffer to the multi buffer"
 9429            );
 9430            assert!(!active_item.is_singleton(cx));
 9431        })
 9432        .unwrap();
 9433}
 9434
 9435#[gpui::test]
 9436async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 9437    init_test(cx, |_| {});
 9438
 9439    let mut cx = EditorTestContext::new(cx).await;
 9440
 9441    let diff_base = r#"
 9442        use some::mod;
 9443
 9444        const A: u32 = 42;
 9445
 9446        fn main() {
 9447            println!("hello");
 9448
 9449            println!("world");
 9450        }
 9451        "#
 9452    .unindent();
 9453
 9454    cx.set_state(
 9455        &r#"
 9456        use some::modified;
 9457
 9458        ˇ
 9459        fn main() {
 9460            println!("hello there");
 9461
 9462            println!("around the");
 9463            println!("world");
 9464        }
 9465        "#
 9466        .unindent(),
 9467    );
 9468
 9469    cx.set_diff_base(Some(&diff_base));
 9470    executor.run_until_parked();
 9471    let unexpanded_hunks = vec![
 9472        (
 9473            "use some::mod;\n".to_string(),
 9474            DiffHunkStatus::Modified,
 9475            0..1,
 9476        ),
 9477        (
 9478            "const A: u32 = 42;\n".to_string(),
 9479            DiffHunkStatus::Removed,
 9480            2..2,
 9481        ),
 9482        (
 9483            "    println!(\"hello\");\n".to_string(),
 9484            DiffHunkStatus::Modified,
 9485            4..5,
 9486        ),
 9487        ("".to_string(), DiffHunkStatus::Added, 6..7),
 9488    ];
 9489    cx.update_editor(|editor, cx| {
 9490        let snapshot = editor.snapshot(cx);
 9491        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9492        assert_eq!(all_hunks, unexpanded_hunks);
 9493    });
 9494
 9495    cx.update_editor(|editor, cx| {
 9496        for _ in 0..4 {
 9497            editor.go_to_hunk(&GoToHunk, cx);
 9498            editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
 9499        }
 9500    });
 9501    executor.run_until_parked();
 9502    cx.assert_editor_state(
 9503        &r#"
 9504        use some::modified;
 9505
 9506        ˇ
 9507        fn main() {
 9508            println!("hello there");
 9509
 9510            println!("around the");
 9511            println!("world");
 9512        }
 9513        "#
 9514        .unindent(),
 9515    );
 9516    cx.update_editor(|editor, cx| {
 9517        let snapshot = editor.snapshot(cx);
 9518        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9519        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9520        assert_eq!(
 9521            expanded_hunks_background_highlights(editor, &snapshot),
 9522            vec![1..2, 7..8, 9..10],
 9523            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
 9524        );
 9525        assert_eq!(
 9526            all_hunks,
 9527            vec![
 9528                ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, 1..2),
 9529                ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, 4..4),
 9530                ("    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, 7..8),
 9531                ("".to_string(), DiffHunkStatus::Added, 9..10),
 9532            ],
 9533            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
 9534            (from modified and removed hunks)"
 9535        );
 9536        assert_eq!(
 9537            all_hunks, all_expanded_hunks,
 9538            "Editor hunks should not change and all be expanded"
 9539        );
 9540    });
 9541
 9542    cx.update_editor(|editor, cx| {
 9543        editor.cancel(&Cancel, cx);
 9544
 9545        let snapshot = editor.snapshot(cx);
 9546        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9547        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
 9548        assert_eq!(
 9549            expanded_hunks_background_highlights(editor, &snapshot),
 9550            Vec::new(),
 9551            "After cancelling in editor, no git highlights should be left"
 9552        );
 9553        assert_eq!(
 9554            all_expanded_hunks,
 9555            Vec::new(),
 9556            "After cancelling in editor, no hunks should be expanded"
 9557        );
 9558        assert_eq!(
 9559            all_hunks, unexpanded_hunks,
 9560            "After cancelling in editor, regular hunks' coordinates should get back to normal"
 9561        );
 9562    });
 9563}
 9564
 9565#[gpui::test]
 9566async fn test_toggled_diff_base_change(
 9567    executor: BackgroundExecutor,
 9568    cx: &mut gpui::TestAppContext,
 9569) {
 9570    init_test(cx, |_| {});
 9571
 9572    let mut cx = EditorTestContext::new(cx).await;
 9573
 9574    let diff_base = r#"
 9575        use some::mod1;
 9576        use some::mod2;
 9577
 9578        const A: u32 = 42;
 9579        const B: u32 = 42;
 9580        const C: u32 = 42;
 9581
 9582        fn main(ˇ) {
 9583            println!("hello");
 9584
 9585            println!("world");
 9586        }
 9587        "#
 9588    .unindent();
 9589
 9590    cx.set_state(
 9591        &r#"
 9592        use some::mod2;
 9593
 9594        const A: u32 = 42;
 9595        const C: u32 = 42;
 9596
 9597        fn main(ˇ) {
 9598            //println!("hello");
 9599
 9600            println!("world");
 9601            //
 9602            //
 9603        }
 9604        "#
 9605        .unindent(),
 9606    );
 9607
 9608    cx.set_diff_base(Some(&diff_base));
 9609    executor.run_until_parked();
 9610    cx.update_editor(|editor, cx| {
 9611        let snapshot = editor.snapshot(cx);
 9612        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9613        assert_eq!(
 9614            all_hunks,
 9615            vec![
 9616                (
 9617                    "use some::mod1;\n".to_string(),
 9618                    DiffHunkStatus::Removed,
 9619                    0..0
 9620                ),
 9621                (
 9622                    "const B: u32 = 42;\n".to_string(),
 9623                    DiffHunkStatus::Removed,
 9624                    3..3
 9625                ),
 9626                (
 9627                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9628                    DiffHunkStatus::Modified,
 9629                    5..7
 9630                ),
 9631                ("".to_string(), DiffHunkStatus::Added, 9..11),
 9632            ]
 9633        );
 9634    });
 9635
 9636    cx.update_editor(|editor, cx| {
 9637        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
 9638    });
 9639    executor.run_until_parked();
 9640    cx.assert_editor_state(
 9641        &r#"
 9642        use some::mod2;
 9643
 9644        const A: u32 = 42;
 9645        const C: u32 = 42;
 9646
 9647        fn main(ˇ) {
 9648            //println!("hello");
 9649
 9650            println!("world");
 9651            //
 9652            //
 9653        }
 9654        "#
 9655        .unindent(),
 9656    );
 9657    cx.update_editor(|editor, cx| {
 9658        let snapshot = editor.snapshot(cx);
 9659        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9660        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9661        assert_eq!(
 9662            expanded_hunks_background_highlights(editor, &snapshot),
 9663            vec![9..11, 13..15],
 9664            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
 9665        );
 9666        assert_eq!(
 9667            all_hunks,
 9668            vec![
 9669                ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, 1..1),
 9670                ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, 5..5),
 9671                ("fn main(ˇ) {\n    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, 9..11),
 9672                ("".to_string(), DiffHunkStatus::Added, 13..15),
 9673            ],
 9674            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
 9675            (from modified and removed hunks)"
 9676        );
 9677        assert_eq!(
 9678            all_hunks, all_expanded_hunks,
 9679            "Editor hunks should not change and all be expanded"
 9680        );
 9681    });
 9682
 9683    cx.set_diff_base(Some("new diff base!"));
 9684    executor.run_until_parked();
 9685
 9686    cx.update_editor(|editor, cx| {
 9687        let snapshot = editor.snapshot(cx);
 9688        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9689        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
 9690        assert_eq!(
 9691            expanded_hunks_background_highlights(editor, &snapshot),
 9692            Vec::new(),
 9693            "After diff base is changed, old git highlights should be removed"
 9694        );
 9695        assert_eq!(
 9696            all_expanded_hunks,
 9697            Vec::new(),
 9698            "After diff base is changed, old git hunk expansions should be removed"
 9699        );
 9700        assert_eq!(
 9701            all_hunks,
 9702            vec![(
 9703                "new diff base!".to_string(),
 9704                DiffHunkStatus::Modified,
 9705                0..snapshot.display_snapshot.max_point().row()
 9706            )],
 9707            "After diff base is changed, hunks should update"
 9708        );
 9709    });
 9710}
 9711
 9712#[gpui::test]
 9713async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 9714    init_test(cx, |_| {});
 9715
 9716    let mut cx = EditorTestContext::new(cx).await;
 9717
 9718    let diff_base = r#"
 9719        use some::mod1;
 9720        use some::mod2;
 9721
 9722        const A: u32 = 42;
 9723        const B: u32 = 42;
 9724        const C: u32 = 42;
 9725
 9726        fn main(ˇ) {
 9727            println!("hello");
 9728
 9729            println!("world");
 9730        }
 9731
 9732        fn another() {
 9733            println!("another");
 9734        }
 9735
 9736        fn another2() {
 9737            println!("another2");
 9738        }
 9739        "#
 9740    .unindent();
 9741
 9742    cx.set_state(
 9743        &r#"
 9744        «use some::mod2;
 9745
 9746        const A: u32 = 42;
 9747        const C: u32 = 42;
 9748
 9749        fn main() {
 9750            //println!("hello");
 9751
 9752            println!("world");
 9753            //
 9754            //ˇ»
 9755        }
 9756
 9757        fn another() {
 9758            println!("another");
 9759            println!("another");
 9760        }
 9761
 9762            println!("another2");
 9763        }
 9764        "#
 9765        .unindent(),
 9766    );
 9767
 9768    cx.set_diff_base(Some(&diff_base));
 9769    executor.run_until_parked();
 9770    cx.update_editor(|editor, cx| {
 9771        let snapshot = editor.snapshot(cx);
 9772        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9773        assert_eq!(
 9774            all_hunks,
 9775            vec![
 9776                (
 9777                    "use some::mod1;\n".to_string(),
 9778                    DiffHunkStatus::Removed,
 9779                    0..0
 9780                ),
 9781                (
 9782                    "const B: u32 = 42;\n".to_string(),
 9783                    DiffHunkStatus::Removed,
 9784                    3..3
 9785                ),
 9786                (
 9787                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9788                    DiffHunkStatus::Modified,
 9789                    5..7
 9790                ),
 9791                ("".to_string(), DiffHunkStatus::Added, 9..11),
 9792                ("".to_string(), DiffHunkStatus::Added, 15..16),
 9793                (
 9794                    "fn another2() {\n".to_string(),
 9795                    DiffHunkStatus::Removed,
 9796                    18..18
 9797                ),
 9798            ]
 9799        );
 9800    });
 9801
 9802    cx.update_editor(|editor, cx| {
 9803        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
 9804    });
 9805    executor.run_until_parked();
 9806    cx.assert_editor_state(
 9807        &r#"
 9808        «use some::mod2;
 9809
 9810        const A: u32 = 42;
 9811        const C: u32 = 42;
 9812
 9813        fn main() {
 9814            //println!("hello");
 9815
 9816            println!("world");
 9817            //
 9818            //ˇ»
 9819        }
 9820
 9821        fn another() {
 9822            println!("another");
 9823            println!("another");
 9824        }
 9825
 9826            println!("another2");
 9827        }
 9828        "#
 9829        .unindent(),
 9830    );
 9831    cx.update_editor(|editor, cx| {
 9832        let snapshot = editor.snapshot(cx);
 9833        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9834        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9835        assert_eq!(
 9836            expanded_hunks_background_highlights(editor, &snapshot),
 9837            vec![9..11, 13..15, 19..20]
 9838        );
 9839        assert_eq!(
 9840            all_hunks,
 9841            vec![
 9842                (
 9843                    "use some::mod1;\n".to_string(),
 9844                    DiffHunkStatus::Removed,
 9845                    1..1
 9846                ),
 9847                (
 9848                    "const B: u32 = 42;\n".to_string(),
 9849                    DiffHunkStatus::Removed,
 9850                    5..5
 9851                ),
 9852                (
 9853                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9854                    DiffHunkStatus::Modified,
 9855                    9..11
 9856                ),
 9857                ("".to_string(), DiffHunkStatus::Added, 13..15),
 9858                ("".to_string(), DiffHunkStatus::Added, 19..20),
 9859                (
 9860                    "fn another2() {\n".to_string(),
 9861                    DiffHunkStatus::Removed,
 9862                    23..23
 9863                ),
 9864            ],
 9865        );
 9866        assert_eq!(all_hunks, all_expanded_hunks);
 9867    });
 9868
 9869    cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
 9870    cx.executor().run_until_parked();
 9871    cx.assert_editor_state(
 9872        &r#"
 9873        «use some::mod2;
 9874
 9875        const A: u32 = 42;
 9876        const C: u32 = 42;
 9877
 9878        fn main() {
 9879            //println!("hello");
 9880
 9881            println!("world");
 9882            //
 9883            //ˇ»
 9884        }
 9885
 9886        fn another() {
 9887            println!("another");
 9888            println!("another");
 9889        }
 9890
 9891            println!("another2");
 9892        }
 9893        "#
 9894        .unindent(),
 9895    );
 9896    cx.update_editor(|editor, cx| {
 9897        let snapshot = editor.snapshot(cx);
 9898        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9899        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9900        assert_eq!(
 9901            expanded_hunks_background_highlights(editor, &snapshot),
 9902            vec![5..6],
 9903            "Only one hunk is left not folded, its highlight should be visible"
 9904        );
 9905        assert_eq!(
 9906            all_hunks,
 9907            vec![
 9908                (
 9909                    "use some::mod1;\n".to_string(),
 9910                    DiffHunkStatus::Removed,
 9911                    0..0
 9912                ),
 9913                (
 9914                    "const B: u32 = 42;\n".to_string(),
 9915                    DiffHunkStatus::Removed,
 9916                    0..0
 9917                ),
 9918                (
 9919                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9920                    DiffHunkStatus::Modified,
 9921                    0..0
 9922                ),
 9923                ("".to_string(), DiffHunkStatus::Added, 0..1),
 9924                ("".to_string(), DiffHunkStatus::Added, 5..6),
 9925                (
 9926                    "fn another2() {\n".to_string(),
 9927                    DiffHunkStatus::Removed,
 9928                    9..9
 9929                ),
 9930            ],
 9931            "Hunk list should still return shifted folded hunks"
 9932        );
 9933        assert_eq!(
 9934            all_expanded_hunks,
 9935            vec![
 9936                ("".to_string(), DiffHunkStatus::Added, 5..6),
 9937                (
 9938                    "fn another2() {\n".to_string(),
 9939                    DiffHunkStatus::Removed,
 9940                    9..9
 9941                ),
 9942            ],
 9943            "Only non-folded hunks should be left expanded"
 9944        );
 9945    });
 9946
 9947    cx.update_editor(|editor, cx| {
 9948        editor.select_all(&SelectAll, cx);
 9949        editor.unfold_lines(&UnfoldLines, cx);
 9950    });
 9951    cx.executor().run_until_parked();
 9952    cx.assert_editor_state(
 9953        &r#"
 9954        «use some::mod2;
 9955
 9956        const A: u32 = 42;
 9957        const C: u32 = 42;
 9958
 9959        fn main() {
 9960            //println!("hello");
 9961
 9962            println!("world");
 9963            //
 9964            //
 9965        }
 9966
 9967        fn another() {
 9968            println!("another");
 9969            println!("another");
 9970        }
 9971
 9972            println!("another2");
 9973        }
 9974        ˇ»"#
 9975        .unindent(),
 9976    );
 9977    cx.update_editor(|editor, cx| {
 9978        let snapshot = editor.snapshot(cx);
 9979        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9980        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9981        assert_eq!(
 9982            expanded_hunks_background_highlights(editor, &snapshot),
 9983            vec![9..11, 13..15, 19..20],
 9984            "After unfolding, all hunk diffs should be visible again"
 9985        );
 9986        assert_eq!(
 9987            all_hunks,
 9988            vec![
 9989                (
 9990                    "use some::mod1;\n".to_string(),
 9991                    DiffHunkStatus::Removed,
 9992                    1..1
 9993                ),
 9994                (
 9995                    "const B: u32 = 42;\n".to_string(),
 9996                    DiffHunkStatus::Removed,
 9997                    5..5
 9998                ),
 9999                (
10000                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
10001                    DiffHunkStatus::Modified,
10002                    9..11
10003                ),
10004                ("".to_string(), DiffHunkStatus::Added, 13..15),
10005                ("".to_string(), DiffHunkStatus::Added, 19..20),
10006                (
10007                    "fn another2() {\n".to_string(),
10008                    DiffHunkStatus::Removed,
10009                    23..23
10010                ),
10011            ],
10012        );
10013        assert_eq!(all_hunks, all_expanded_hunks);
10014    });
10015}
10016
10017#[gpui::test]
10018async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10019    init_test(cx, |_| {});
10020
10021    let cols = 4;
10022    let rows = 10;
10023    let sample_text_1 = sample_text(rows, cols, 'a');
10024    assert_eq!(
10025        sample_text_1,
10026        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10027    );
10028    let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10029    let sample_text_2 = sample_text(rows, cols, 'l');
10030    assert_eq!(
10031        sample_text_2,
10032        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10033    );
10034    let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10035    let sample_text_3 = sample_text(rows, cols, 'v');
10036    assert_eq!(
10037        sample_text_3,
10038        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10039    );
10040    let modified_sample_text_3 =
10041        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10042    let buffer_1 = cx.new_model(|cx| {
10043        let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10044        buffer.set_diff_base(Some(sample_text_1.clone().into()), cx);
10045        buffer
10046    });
10047    let buffer_2 = cx.new_model(|cx| {
10048        let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10049        buffer.set_diff_base(Some(sample_text_2.clone().into()), cx);
10050        buffer
10051    });
10052    let buffer_3 = cx.new_model(|cx| {
10053        let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10054        buffer.set_diff_base(Some(sample_text_3.clone().into()), cx);
10055        buffer
10056    });
10057
10058    let multi_buffer = cx.new_model(|cx| {
10059        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10060        multibuffer.push_excerpts(
10061            buffer_1.clone(),
10062            [
10063                ExcerptRange {
10064                    context: Point::new(0, 0)..Point::new(3, 0),
10065                    primary: None,
10066                },
10067                ExcerptRange {
10068                    context: Point::new(5, 0)..Point::new(7, 0),
10069                    primary: None,
10070                },
10071                ExcerptRange {
10072                    context: Point::new(9, 0)..Point::new(10, 4),
10073                    primary: None,
10074                },
10075            ],
10076            cx,
10077        );
10078        multibuffer.push_excerpts(
10079            buffer_2.clone(),
10080            [
10081                ExcerptRange {
10082                    context: Point::new(0, 0)..Point::new(3, 0),
10083                    primary: None,
10084                },
10085                ExcerptRange {
10086                    context: Point::new(5, 0)..Point::new(7, 0),
10087                    primary: None,
10088                },
10089                ExcerptRange {
10090                    context: Point::new(9, 0)..Point::new(10, 4),
10091                    primary: None,
10092                },
10093            ],
10094            cx,
10095        );
10096        multibuffer.push_excerpts(
10097            buffer_3.clone(),
10098            [
10099                ExcerptRange {
10100                    context: Point::new(0, 0)..Point::new(3, 0),
10101                    primary: None,
10102                },
10103                ExcerptRange {
10104                    context: Point::new(5, 0)..Point::new(7, 0),
10105                    primary: None,
10106                },
10107                ExcerptRange {
10108                    context: Point::new(9, 0)..Point::new(10, 4),
10109                    primary: None,
10110                },
10111            ],
10112            cx,
10113        );
10114        multibuffer
10115    });
10116
10117    let fs = FakeFs::new(cx.executor());
10118    fs.insert_tree(
10119        "/a",
10120        json!({
10121            "main.rs": modified_sample_text_1,
10122            "other.rs": modified_sample_text_2,
10123            "lib.rs": modified_sample_text_3,
10124        }),
10125    )
10126    .await;
10127
10128    let project = Project::test(fs, ["/a".as_ref()], cx).await;
10129    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10130    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10131    let multi_buffer_editor =
10132        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
10133    cx.executor().run_until_parked();
10134
10135    let expected_all_hunks = vec![
10136        ("bbbb\n".to_string(), DiffHunkStatus::Removed, 3..3),
10137        ("nnnn\n".to_string(), DiffHunkStatus::Modified, 16..17),
10138        ("".to_string(), DiffHunkStatus::Added, 31..32),
10139    ];
10140    let expected_all_hunks_shifted = vec![
10141        ("bbbb\n".to_string(), DiffHunkStatus::Removed, 4..4),
10142        ("nnnn\n".to_string(), DiffHunkStatus::Modified, 18..19),
10143        ("".to_string(), DiffHunkStatus::Added, 33..34),
10144    ];
10145
10146    multi_buffer_editor.update(cx, |editor, cx| {
10147        let snapshot = editor.snapshot(cx);
10148        let all_hunks = editor_hunks(editor, &snapshot, cx);
10149        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10150        assert_eq!(
10151            expanded_hunks_background_highlights(editor, &snapshot),
10152            Vec::new(),
10153        );
10154        assert_eq!(all_hunks, expected_all_hunks);
10155        assert_eq!(all_expanded_hunks, Vec::new());
10156    });
10157
10158    multi_buffer_editor.update(cx, |editor, cx| {
10159        editor.select_all(&SelectAll, cx);
10160        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10161    });
10162    cx.executor().run_until_parked();
10163    multi_buffer_editor.update(cx, |editor, cx| {
10164        let snapshot = editor.snapshot(cx);
10165        let all_hunks = editor_hunks(editor, &snapshot, cx);
10166        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10167        assert_eq!(
10168            expanded_hunks_background_highlights(editor, &snapshot),
10169            vec![18..19, 33..34],
10170        );
10171        assert_eq!(all_hunks, expected_all_hunks_shifted);
10172        assert_eq!(all_hunks, all_expanded_hunks);
10173    });
10174
10175    multi_buffer_editor.update(cx, |editor, cx| {
10176        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10177    });
10178    cx.executor().run_until_parked();
10179    multi_buffer_editor.update(cx, |editor, cx| {
10180        let snapshot = editor.snapshot(cx);
10181        let all_hunks = editor_hunks(editor, &snapshot, cx);
10182        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10183        assert_eq!(
10184            expanded_hunks_background_highlights(editor, &snapshot),
10185            Vec::new(),
10186        );
10187        assert_eq!(all_hunks, expected_all_hunks);
10188        assert_eq!(all_expanded_hunks, Vec::new());
10189    });
10190
10191    multi_buffer_editor.update(cx, |editor, cx| {
10192        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10193    });
10194    cx.executor().run_until_parked();
10195    multi_buffer_editor.update(cx, |editor, cx| {
10196        let snapshot = editor.snapshot(cx);
10197        let all_hunks = editor_hunks(editor, &snapshot, cx);
10198        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10199        assert_eq!(
10200            expanded_hunks_background_highlights(editor, &snapshot),
10201            vec![18..19, 33..34],
10202        );
10203        assert_eq!(all_hunks, expected_all_hunks_shifted);
10204        assert_eq!(all_hunks, all_expanded_hunks);
10205    });
10206
10207    multi_buffer_editor.update(cx, |editor, cx| {
10208        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10209    });
10210    cx.executor().run_until_parked();
10211    multi_buffer_editor.update(cx, |editor, cx| {
10212        let snapshot = editor.snapshot(cx);
10213        let all_hunks = editor_hunks(editor, &snapshot, cx);
10214        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10215        assert_eq!(
10216            expanded_hunks_background_highlights(editor, &snapshot),
10217            Vec::new(),
10218        );
10219        assert_eq!(all_hunks, expected_all_hunks);
10220        assert_eq!(all_expanded_hunks, Vec::new());
10221    });
10222}
10223
10224#[gpui::test]
10225async fn test_edits_around_toggled_additions(
10226    executor: BackgroundExecutor,
10227    cx: &mut gpui::TestAppContext,
10228) {
10229    init_test(cx, |_| {});
10230
10231    let mut cx = EditorTestContext::new(cx).await;
10232
10233    let diff_base = r#"
10234        use some::mod1;
10235        use some::mod2;
10236
10237        const A: u32 = 42;
10238
10239        fn main() {
10240            println!("hello");
10241
10242            println!("world");
10243        }
10244        "#
10245    .unindent();
10246    executor.run_until_parked();
10247    cx.set_state(
10248        &r#"
10249        use some::mod1;
10250        use some::mod2;
10251
10252        const A: u32 = 42;
10253        const B: u32 = 42;
10254        const C: u32 = 42;
10255        ˇ
10256
10257        fn main() {
10258            println!("hello");
10259
10260            println!("world");
10261        }
10262        "#
10263        .unindent(),
10264    );
10265
10266    cx.set_diff_base(Some(&diff_base));
10267    executor.run_until_parked();
10268    cx.update_editor(|editor, cx| {
10269        let snapshot = editor.snapshot(cx);
10270        let all_hunks = editor_hunks(editor, &snapshot, cx);
10271        assert_eq!(
10272            all_hunks,
10273            vec![("".to_string(), DiffHunkStatus::Added, 4..7)]
10274        );
10275    });
10276    cx.update_editor(|editor, cx| {
10277        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10278    });
10279    executor.run_until_parked();
10280    cx.assert_editor_state(
10281        &r#"
10282        use some::mod1;
10283        use some::mod2;
10284
10285        const A: u32 = 42;
10286        const B: u32 = 42;
10287        const C: u32 = 42;
10288        ˇ
10289
10290        fn main() {
10291            println!("hello");
10292
10293            println!("world");
10294        }
10295        "#
10296        .unindent(),
10297    );
10298    cx.update_editor(|editor, cx| {
10299        let snapshot = editor.snapshot(cx);
10300        let all_hunks = editor_hunks(editor, &snapshot, cx);
10301        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10302        assert_eq!(
10303            all_hunks,
10304            vec![("".to_string(), DiffHunkStatus::Added, 4..7)]
10305        );
10306        assert_eq!(
10307            expanded_hunks_background_highlights(editor, &snapshot),
10308            vec![4..7]
10309        );
10310        assert_eq!(all_hunks, all_expanded_hunks);
10311    });
10312
10313    cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10314    executor.run_until_parked();
10315    cx.assert_editor_state(
10316        &r#"
10317        use some::mod1;
10318        use some::mod2;
10319
10320        const A: u32 = 42;
10321        const B: u32 = 42;
10322        const C: u32 = 42;
10323        const D: u32 = 42;
10324        ˇ
10325
10326        fn main() {
10327            println!("hello");
10328
10329            println!("world");
10330        }
10331        "#
10332        .unindent(),
10333    );
10334    cx.update_editor(|editor, cx| {
10335        let snapshot = editor.snapshot(cx);
10336        let all_hunks = editor_hunks(editor, &snapshot, cx);
10337        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10338        assert_eq!(
10339            all_hunks,
10340            vec![("".to_string(), DiffHunkStatus::Added, 4..8)]
10341        );
10342        assert_eq!(
10343            expanded_hunks_background_highlights(editor, &snapshot),
10344            vec![4..8],
10345            "Edited hunk should have one more line added"
10346        );
10347        assert_eq!(
10348            all_hunks, all_expanded_hunks,
10349            "Expanded hunk should also grow with the addition"
10350        );
10351    });
10352
10353    cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
10354    executor.run_until_parked();
10355    cx.assert_editor_state(
10356        &r#"
10357        use some::mod1;
10358        use some::mod2;
10359
10360        const A: u32 = 42;
10361        const B: u32 = 42;
10362        const C: u32 = 42;
10363        const D: u32 = 42;
10364        const E: u32 = 42;
10365        ˇ
10366
10367        fn main() {
10368            println!("hello");
10369
10370            println!("world");
10371        }
10372        "#
10373        .unindent(),
10374    );
10375    cx.update_editor(|editor, cx| {
10376        let snapshot = editor.snapshot(cx);
10377        let all_hunks = editor_hunks(editor, &snapshot, cx);
10378        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10379        assert_eq!(
10380            all_hunks,
10381            vec![("".to_string(), DiffHunkStatus::Added, 4..9)]
10382        );
10383        assert_eq!(
10384            expanded_hunks_background_highlights(editor, &snapshot),
10385            vec![4..9],
10386            "Edited hunk should have one more line added"
10387        );
10388        assert_eq!(all_hunks, all_expanded_hunks);
10389    });
10390
10391    cx.update_editor(|editor, cx| {
10392        editor.move_up(&MoveUp, cx);
10393        editor.delete_line(&DeleteLine, cx);
10394    });
10395    executor.run_until_parked();
10396    cx.assert_editor_state(
10397        &r#"
10398        use some::mod1;
10399        use some::mod2;
10400
10401        const A: u32 = 42;
10402        const B: u32 = 42;
10403        const C: u32 = 42;
10404        const D: u32 = 42;
10405        ˇ
10406
10407        fn main() {
10408            println!("hello");
10409
10410            println!("world");
10411        }
10412        "#
10413        .unindent(),
10414    );
10415    cx.update_editor(|editor, cx| {
10416        let snapshot = editor.snapshot(cx);
10417        let all_hunks = editor_hunks(editor, &snapshot, cx);
10418        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10419        assert_eq!(
10420            all_hunks,
10421            vec![("".to_string(), DiffHunkStatus::Added, 4..8)]
10422        );
10423        assert_eq!(
10424            expanded_hunks_background_highlights(editor, &snapshot),
10425            vec![4..8],
10426            "Deleting a line should shrint the hunk"
10427        );
10428        assert_eq!(
10429            all_hunks, all_expanded_hunks,
10430            "Expanded hunk should also shrink with the addition"
10431        );
10432    });
10433
10434    cx.update_editor(|editor, cx| {
10435        editor.move_up(&MoveUp, cx);
10436        editor.delete_line(&DeleteLine, cx);
10437        editor.move_up(&MoveUp, cx);
10438        editor.delete_line(&DeleteLine, cx);
10439        editor.move_up(&MoveUp, cx);
10440        editor.delete_line(&DeleteLine, cx);
10441    });
10442    executor.run_until_parked();
10443    cx.assert_editor_state(
10444        &r#"
10445        use some::mod1;
10446        use some::mod2;
10447
10448        const A: u32 = 42;
10449        ˇ
10450
10451        fn main() {
10452            println!("hello");
10453
10454            println!("world");
10455        }
10456        "#
10457        .unindent(),
10458    );
10459    cx.update_editor(|editor, cx| {
10460        let snapshot = editor.snapshot(cx);
10461        let all_hunks = editor_hunks(editor, &snapshot, cx);
10462        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10463        assert_eq!(
10464            all_hunks,
10465            vec![("".to_string(), DiffHunkStatus::Added, 5..6)]
10466        );
10467        assert_eq!(
10468            expanded_hunks_background_highlights(editor, &snapshot),
10469            vec![5..6]
10470        );
10471        assert_eq!(all_hunks, all_expanded_hunks);
10472    });
10473
10474    cx.update_editor(|editor, cx| {
10475        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
10476        editor.delete_line(&DeleteLine, cx);
10477    });
10478    executor.run_until_parked();
10479    cx.assert_editor_state(
10480        &r#"
10481        ˇ
10482
10483        fn main() {
10484            println!("hello");
10485
10486            println!("world");
10487        }
10488        "#
10489        .unindent(),
10490    );
10491    cx.update_editor(|editor, cx| {
10492        let snapshot = editor.snapshot(cx);
10493        let all_hunks = editor_hunks(editor, &snapshot, cx);
10494        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10495        assert_eq!(
10496            all_hunks,
10497            vec![
10498                (
10499                    "use some::mod1;\nuse some::mod2;\n".to_string(),
10500                    DiffHunkStatus::Removed,
10501                    0..0
10502                ),
10503                (
10504                    "const A: u32 = 42;\n".to_string(),
10505                    DiffHunkStatus::Removed,
10506                    2..2
10507                )
10508            ]
10509        );
10510        assert_eq!(
10511            expanded_hunks_background_highlights(editor, &snapshot),
10512            Vec::new(),
10513            "Should close all stale expanded addition hunks"
10514        );
10515        assert_eq!(
10516            all_expanded_hunks,
10517            vec![(
10518                "const A: u32 = 42;\n".to_string(),
10519                DiffHunkStatus::Removed,
10520                2..2
10521            )],
10522            "Should open hunks that were adjacent to the stale addition one"
10523        );
10524    });
10525}
10526
10527#[gpui::test]
10528async fn test_edits_around_toggled_deletions(
10529    executor: BackgroundExecutor,
10530    cx: &mut gpui::TestAppContext,
10531) {
10532    init_test(cx, |_| {});
10533
10534    let mut cx = EditorTestContext::new(cx).await;
10535
10536    let diff_base = r#"
10537        use some::mod1;
10538        use some::mod2;
10539
10540        const A: u32 = 42;
10541        const B: u32 = 42;
10542        const C: u32 = 42;
10543
10544
10545        fn main() {
10546            println!("hello");
10547
10548            println!("world");
10549        }
10550        "#
10551    .unindent();
10552    executor.run_until_parked();
10553    cx.set_state(
10554        &r#"
10555        use some::mod1;
10556        use some::mod2;
10557
10558        ˇconst B: u32 = 42;
10559        const C: u32 = 42;
10560
10561
10562        fn main() {
10563            println!("hello");
10564
10565            println!("world");
10566        }
10567        "#
10568        .unindent(),
10569    );
10570
10571    cx.set_diff_base(Some(&diff_base));
10572    executor.run_until_parked();
10573    cx.update_editor(|editor, cx| {
10574        let snapshot = editor.snapshot(cx);
10575        let all_hunks = editor_hunks(editor, &snapshot, cx);
10576        assert_eq!(
10577            all_hunks,
10578            vec![(
10579                "const A: u32 = 42;\n".to_string(),
10580                DiffHunkStatus::Removed,
10581                3..3
10582            )]
10583        );
10584    });
10585    cx.update_editor(|editor, cx| {
10586        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10587    });
10588    executor.run_until_parked();
10589    cx.assert_editor_state(
10590        &r#"
10591        use some::mod1;
10592        use some::mod2;
10593
10594        ˇconst B: u32 = 42;
10595        const C: u32 = 42;
10596
10597
10598        fn main() {
10599            println!("hello");
10600
10601            println!("world");
10602        }
10603        "#
10604        .unindent(),
10605    );
10606    cx.update_editor(|editor, cx| {
10607        let snapshot = editor.snapshot(cx);
10608        let all_hunks = editor_hunks(editor, &snapshot, cx);
10609        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10610        assert_eq!(
10611            expanded_hunks_background_highlights(editor, &snapshot),
10612            Vec::new()
10613        );
10614        assert_eq!(
10615            all_hunks,
10616            vec![(
10617                "const A: u32 = 42;\n".to_string(),
10618                DiffHunkStatus::Removed,
10619                4..4
10620            )]
10621        );
10622        assert_eq!(all_hunks, all_expanded_hunks);
10623    });
10624
10625    cx.update_editor(|editor, cx| {
10626        editor.delete_line(&DeleteLine, cx);
10627    });
10628    executor.run_until_parked();
10629    cx.assert_editor_state(
10630        &r#"
10631        use some::mod1;
10632        use some::mod2;
10633
10634        ˇconst C: u32 = 42;
10635
10636
10637        fn main() {
10638            println!("hello");
10639
10640            println!("world");
10641        }
10642        "#
10643        .unindent(),
10644    );
10645    cx.update_editor(|editor, cx| {
10646        let snapshot = editor.snapshot(cx);
10647        let all_hunks = editor_hunks(editor, &snapshot, cx);
10648        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10649        assert_eq!(
10650            expanded_hunks_background_highlights(editor, &snapshot),
10651            Vec::new(),
10652            "Deleted hunks do not highlight current editor's background"
10653        );
10654        assert_eq!(
10655            all_hunks,
10656            vec![(
10657                "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
10658                DiffHunkStatus::Removed,
10659                5..5
10660            )]
10661        );
10662        assert_eq!(all_hunks, all_expanded_hunks);
10663    });
10664
10665    cx.update_editor(|editor, cx| {
10666        editor.delete_line(&DeleteLine, cx);
10667    });
10668    executor.run_until_parked();
10669    cx.assert_editor_state(
10670        &r#"
10671        use some::mod1;
10672        use some::mod2;
10673
10674        ˇ
10675
10676        fn main() {
10677            println!("hello");
10678
10679            println!("world");
10680        }
10681        "#
10682        .unindent(),
10683    );
10684    cx.update_editor(|editor, cx| {
10685        let snapshot = editor.snapshot(cx);
10686        let all_hunks = editor_hunks(editor, &snapshot, cx);
10687        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10688        assert_eq!(
10689            expanded_hunks_background_highlights(editor, &snapshot),
10690            Vec::new()
10691        );
10692        assert_eq!(
10693            all_hunks,
10694            vec![(
10695                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10696                DiffHunkStatus::Removed,
10697                6..6
10698            )]
10699        );
10700        assert_eq!(all_hunks, all_expanded_hunks);
10701    });
10702
10703    cx.update_editor(|editor, cx| {
10704        editor.handle_input("replacement", cx);
10705    });
10706    executor.run_until_parked();
10707    cx.assert_editor_state(
10708        &r#"
10709        use some::mod1;
10710        use some::mod2;
10711
10712        replacementˇ
10713
10714        fn main() {
10715            println!("hello");
10716
10717            println!("world");
10718        }
10719        "#
10720        .unindent(),
10721    );
10722    cx.update_editor(|editor, cx| {
10723        let snapshot = editor.snapshot(cx);
10724        let all_hunks = editor_hunks(editor, &snapshot, cx);
10725        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10726        assert_eq!(
10727            all_hunks,
10728            vec![(
10729                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
10730                DiffHunkStatus::Modified,
10731                7..8
10732            )]
10733        );
10734        assert_eq!(
10735            expanded_hunks_background_highlights(editor, &snapshot),
10736            vec![7..8],
10737            "Modified expanded hunks should display additions and highlight their background"
10738        );
10739        assert_eq!(all_hunks, all_expanded_hunks);
10740    });
10741}
10742
10743#[gpui::test]
10744async fn test_edits_around_toggled_modifications(
10745    executor: BackgroundExecutor,
10746    cx: &mut gpui::TestAppContext,
10747) {
10748    init_test(cx, |_| {});
10749
10750    let mut cx = EditorTestContext::new(cx).await;
10751
10752    let diff_base = r#"
10753        use some::mod1;
10754        use some::mod2;
10755
10756        const A: u32 = 42;
10757        const B: u32 = 42;
10758        const C: u32 = 42;
10759        const D: u32 = 42;
10760
10761
10762        fn main() {
10763            println!("hello");
10764
10765            println!("world");
10766        }"#
10767    .unindent();
10768    executor.run_until_parked();
10769    cx.set_state(
10770        &r#"
10771        use some::mod1;
10772        use some::mod2;
10773
10774        const A: u32 = 42;
10775        const B: u32 = 42;
10776        const C: u32 = 43ˇ
10777        const D: u32 = 42;
10778
10779
10780        fn main() {
10781            println!("hello");
10782
10783            println!("world");
10784        }"#
10785        .unindent(),
10786    );
10787
10788    cx.set_diff_base(Some(&diff_base));
10789    executor.run_until_parked();
10790    cx.update_editor(|editor, cx| {
10791        let snapshot = editor.snapshot(cx);
10792        let all_hunks = editor_hunks(editor, &snapshot, cx);
10793        assert_eq!(
10794            all_hunks,
10795            vec![(
10796                "const C: u32 = 42;\n".to_string(),
10797                DiffHunkStatus::Modified,
10798                5..6
10799            )]
10800        );
10801    });
10802    cx.update_editor(|editor, cx| {
10803        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10804    });
10805    executor.run_until_parked();
10806    cx.assert_editor_state(
10807        &r#"
10808        use some::mod1;
10809        use some::mod2;
10810
10811        const A: u32 = 42;
10812        const B: u32 = 42;
10813        const C: u32 = 43ˇ
10814        const D: u32 = 42;
10815
10816
10817        fn main() {
10818            println!("hello");
10819
10820            println!("world");
10821        }"#
10822        .unindent(),
10823    );
10824    cx.update_editor(|editor, cx| {
10825        let snapshot = editor.snapshot(cx);
10826        let all_hunks = editor_hunks(editor, &snapshot, cx);
10827        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10828        assert_eq!(
10829            expanded_hunks_background_highlights(editor, &snapshot),
10830            vec![6..7],
10831        );
10832        assert_eq!(
10833            all_hunks,
10834            vec![(
10835                "const C: u32 = 42;\n".to_string(),
10836                DiffHunkStatus::Modified,
10837                6..7
10838            )]
10839        );
10840        assert_eq!(all_hunks, all_expanded_hunks);
10841    });
10842
10843    cx.update_editor(|editor, cx| {
10844        editor.handle_input("\nnew_line\n", cx);
10845    });
10846    executor.run_until_parked();
10847    cx.assert_editor_state(
10848        &r#"
10849            use some::mod1;
10850            use some::mod2;
10851
10852            const A: u32 = 42;
10853            const B: u32 = 42;
10854            const C: u32 = 43
10855            new_line
10856            ˇ
10857            const D: u32 = 42;
10858
10859
10860            fn main() {
10861                println!("hello");
10862
10863                println!("world");
10864            }"#
10865        .unindent(),
10866    );
10867    cx.update_editor(|editor, cx| {
10868        let snapshot = editor.snapshot(cx);
10869        let all_hunks = editor_hunks(editor, &snapshot, cx);
10870        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10871        assert_eq!(
10872            expanded_hunks_background_highlights(editor, &snapshot),
10873            vec![6..9],
10874            "Modified hunk should grow highlighted lines on more text additions"
10875        );
10876        assert_eq!(
10877            all_hunks,
10878            vec![(
10879                "const C: u32 = 42;\n".to_string(),
10880                DiffHunkStatus::Modified,
10881                6..9
10882            )]
10883        );
10884        assert_eq!(all_hunks, all_expanded_hunks);
10885    });
10886
10887    cx.update_editor(|editor, cx| {
10888        editor.move_up(&MoveUp, cx);
10889        editor.move_up(&MoveUp, cx);
10890        editor.move_up(&MoveUp, cx);
10891        editor.delete_line(&DeleteLine, cx);
10892    });
10893    executor.run_until_parked();
10894    cx.assert_editor_state(
10895        &r#"
10896            use some::mod1;
10897            use some::mod2;
10898
10899            const A: u32 = 42;
10900            ˇconst C: u32 = 43
10901            new_line
10902
10903            const D: u32 = 42;
10904
10905
10906            fn main() {
10907                println!("hello");
10908
10909                println!("world");
10910            }"#
10911        .unindent(),
10912    );
10913    cx.update_editor(|editor, cx| {
10914        let snapshot = editor.snapshot(cx);
10915        let all_hunks = editor_hunks(editor, &snapshot, cx);
10916        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10917        assert_eq!(
10918            expanded_hunks_background_highlights(editor, &snapshot),
10919            vec![6..9],
10920            "Modified hunk should grow deleted lines on text deletions above"
10921        );
10922        assert_eq!(
10923            all_hunks,
10924            vec![(
10925                "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10926                DiffHunkStatus::Modified,
10927                6..9
10928            )]
10929        );
10930        assert_eq!(all_hunks, all_expanded_hunks);
10931    });
10932
10933    cx.update_editor(|editor, cx| {
10934        editor.move_up(&MoveUp, cx);
10935        editor.handle_input("v", cx);
10936    });
10937    executor.run_until_parked();
10938    cx.assert_editor_state(
10939        &r#"
10940            use some::mod1;
10941            use some::mod2;
10942
10943            vˇconst A: u32 = 42;
10944            const C: u32 = 43
10945            new_line
10946
10947            const D: u32 = 42;
10948
10949
10950            fn main() {
10951                println!("hello");
10952
10953                println!("world");
10954            }"#
10955        .unindent(),
10956    );
10957    cx.update_editor(|editor, cx| {
10958        let snapshot = editor.snapshot(cx);
10959        let all_hunks = editor_hunks(editor, &snapshot, cx);
10960        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10961        assert_eq!(
10962            expanded_hunks_background_highlights(editor, &snapshot),
10963            vec![6..10],
10964            "Modified hunk should grow deleted lines on text modifications above"
10965        );
10966        assert_eq!(
10967            all_hunks,
10968            vec![(
10969                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10970                DiffHunkStatus::Modified,
10971                6..10
10972            )]
10973        );
10974        assert_eq!(all_hunks, all_expanded_hunks);
10975    });
10976
10977    cx.update_editor(|editor, cx| {
10978        editor.move_down(&MoveDown, cx);
10979        editor.move_down(&MoveDown, cx);
10980        editor.delete_line(&DeleteLine, cx)
10981    });
10982    executor.run_until_parked();
10983    cx.assert_editor_state(
10984        &r#"
10985            use some::mod1;
10986            use some::mod2;
10987
10988            vconst A: u32 = 42;
10989            const C: u32 = 43
10990            ˇ
10991            const D: u32 = 42;
10992
10993
10994            fn main() {
10995                println!("hello");
10996
10997                println!("world");
10998            }"#
10999        .unindent(),
11000    );
11001    cx.update_editor(|editor, cx| {
11002        let snapshot = editor.snapshot(cx);
11003        let all_hunks = editor_hunks(editor, &snapshot, cx);
11004        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11005        assert_eq!(
11006            expanded_hunks_background_highlights(editor, &snapshot),
11007            vec![6..9],
11008            "Modified hunk should grow shrink lines on modification lines removal"
11009        );
11010        assert_eq!(
11011            all_hunks,
11012            vec![(
11013                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11014                DiffHunkStatus::Modified,
11015                6..9
11016            )]
11017        );
11018        assert_eq!(all_hunks, all_expanded_hunks);
11019    });
11020
11021    cx.update_editor(|editor, cx| {
11022        editor.move_up(&MoveUp, cx);
11023        editor.move_up(&MoveUp, cx);
11024        editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11025        editor.delete_line(&DeleteLine, cx)
11026    });
11027    executor.run_until_parked();
11028    cx.assert_editor_state(
11029        &r#"
11030            use some::mod1;
11031            use some::mod2;
11032
11033            ˇ
11034
11035            fn main() {
11036                println!("hello");
11037
11038                println!("world");
11039            }"#
11040        .unindent(),
11041    );
11042    cx.update_editor(|editor, cx| {
11043        let snapshot = editor.snapshot(cx);
11044        let all_hunks = editor_hunks(editor, &snapshot, cx);
11045        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11046        assert_eq!(
11047            expanded_hunks_background_highlights(editor, &snapshot),
11048            Vec::new(),
11049            "Modified hunk should turn into a removed one on all modified lines removal"
11050        );
11051        assert_eq!(
11052            all_hunks,
11053            vec![(
11054                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11055                    .to_string(),
11056                DiffHunkStatus::Removed,
11057                7..7
11058            )]
11059        );
11060        assert_eq!(all_hunks, all_expanded_hunks);
11061    });
11062}
11063
11064#[gpui::test]
11065async fn test_multiple_expanded_hunks_merge(
11066    executor: BackgroundExecutor,
11067    cx: &mut gpui::TestAppContext,
11068) {
11069    init_test(cx, |_| {});
11070
11071    let mut cx = EditorTestContext::new(cx).await;
11072
11073    let diff_base = r#"
11074        use some::mod1;
11075        use some::mod2;
11076
11077        const A: u32 = 42;
11078        const B: u32 = 42;
11079        const C: u32 = 42;
11080        const D: u32 = 42;
11081
11082
11083        fn main() {
11084            println!("hello");
11085
11086            println!("world");
11087        }"#
11088    .unindent();
11089    executor.run_until_parked();
11090    cx.set_state(
11091        &r#"
11092        use some::mod1;
11093        use some::mod2;
11094
11095        const A: u32 = 42;
11096        const B: u32 = 42;
11097        const C: u32 = 43ˇ
11098        const D: u32 = 42;
11099
11100
11101        fn main() {
11102            println!("hello");
11103
11104            println!("world");
11105        }"#
11106        .unindent(),
11107    );
11108
11109    cx.set_diff_base(Some(&diff_base));
11110    executor.run_until_parked();
11111    cx.update_editor(|editor, cx| {
11112        let snapshot = editor.snapshot(cx);
11113        let all_hunks = editor_hunks(editor, &snapshot, cx);
11114        assert_eq!(
11115            all_hunks,
11116            vec![(
11117                "const C: u32 = 42;\n".to_string(),
11118                DiffHunkStatus::Modified,
11119                5..6
11120            )]
11121        );
11122    });
11123    cx.update_editor(|editor, cx| {
11124        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11125    });
11126    executor.run_until_parked();
11127    cx.assert_editor_state(
11128        &r#"
11129        use some::mod1;
11130        use some::mod2;
11131
11132        const A: u32 = 42;
11133        const B: u32 = 42;
11134        const C: u32 = 43ˇ
11135        const D: u32 = 42;
11136
11137
11138        fn main() {
11139            println!("hello");
11140
11141            println!("world");
11142        }"#
11143        .unindent(),
11144    );
11145    cx.update_editor(|editor, cx| {
11146        let snapshot = editor.snapshot(cx);
11147        let all_hunks = editor_hunks(editor, &snapshot, cx);
11148        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11149        assert_eq!(
11150            expanded_hunks_background_highlights(editor, &snapshot),
11151            vec![6..7],
11152        );
11153        assert_eq!(
11154            all_hunks,
11155            vec![(
11156                "const C: u32 = 42;\n".to_string(),
11157                DiffHunkStatus::Modified,
11158                6..7
11159            )]
11160        );
11161        assert_eq!(all_hunks, all_expanded_hunks);
11162    });
11163
11164    cx.update_editor(|editor, cx| {
11165        editor.handle_input("\nnew_line\n", cx);
11166    });
11167    executor.run_until_parked();
11168    cx.assert_editor_state(
11169        &r#"
11170            use some::mod1;
11171            use some::mod2;
11172
11173            const A: u32 = 42;
11174            const B: u32 = 42;
11175            const C: u32 = 43
11176            new_line
11177            ˇ
11178            const D: u32 = 42;
11179
11180
11181            fn main() {
11182                println!("hello");
11183
11184                println!("world");
11185            }"#
11186        .unindent(),
11187    );
11188}
11189
11190fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
11191    let point = DisplayPoint::new(row as u32, column as u32);
11192    point..point
11193}
11194
11195fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
11196    let (text, ranges) = marked_text_ranges(marked_text, true);
11197    assert_eq!(view.text(cx), text);
11198    assert_eq!(
11199        view.selections.ranges(cx),
11200        ranges,
11201        "Assert selections are {}",
11202        marked_text
11203    );
11204}
11205
11206/// Handle completion request passing a marked string specifying where the completion
11207/// should be triggered from using '|' character, what range should be replaced, and what completions
11208/// should be returned using '<' and '>' to delimit the range
11209pub fn handle_completion_request(
11210    cx: &mut EditorLspTestContext,
11211    marked_string: &str,
11212    completions: Vec<&'static str>,
11213) -> impl Future<Output = ()> {
11214    let complete_from_marker: TextRangeMarker = '|'.into();
11215    let replace_range_marker: TextRangeMarker = ('<', '>').into();
11216    let (_, mut marked_ranges) = marked_text_ranges_by(
11217        marked_string,
11218        vec![complete_from_marker.clone(), replace_range_marker.clone()],
11219    );
11220
11221    let complete_from_position =
11222        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
11223    let replace_range =
11224        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
11225
11226    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
11227        let completions = completions.clone();
11228        async move {
11229            assert_eq!(params.text_document_position.text_document.uri, url.clone());
11230            assert_eq!(
11231                params.text_document_position.position,
11232                complete_from_position
11233            );
11234            Ok(Some(lsp::CompletionResponse::Array(
11235                completions
11236                    .iter()
11237                    .map(|completion_text| lsp::CompletionItem {
11238                        label: completion_text.to_string(),
11239                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11240                            range: replace_range,
11241                            new_text: completion_text.to_string(),
11242                        })),
11243                        ..Default::default()
11244                    })
11245                    .collect(),
11246            )))
11247        }
11248    });
11249
11250    async move {
11251        request.next().await;
11252    }
11253}
11254
11255fn handle_resolve_completion_request(
11256    cx: &mut EditorLspTestContext,
11257    edits: Option<Vec<(&'static str, &'static str)>>,
11258) -> impl Future<Output = ()> {
11259    let edits = edits.map(|edits| {
11260        edits
11261            .iter()
11262            .map(|(marked_string, new_text)| {
11263                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
11264                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
11265                lsp::TextEdit::new(replace_range, new_text.to_string())
11266            })
11267            .collect::<Vec<_>>()
11268    });
11269
11270    let mut request =
11271        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11272            let edits = edits.clone();
11273            async move {
11274                Ok(lsp::CompletionItem {
11275                    additional_text_edits: edits,
11276                    ..Default::default()
11277                })
11278            }
11279        });
11280
11281    async move {
11282        request.next().await;
11283    }
11284}
11285
11286pub(crate) fn update_test_language_settings(
11287    cx: &mut TestAppContext,
11288    f: impl Fn(&mut AllLanguageSettingsContent),
11289) {
11290    _ = cx.update(|cx| {
11291        cx.update_global(|store: &mut SettingsStore, cx| {
11292            store.update_user_settings::<AllLanguageSettings>(cx, f);
11293        });
11294    });
11295}
11296
11297pub(crate) fn update_test_project_settings(
11298    cx: &mut TestAppContext,
11299    f: impl Fn(&mut ProjectSettings),
11300) {
11301    _ = cx.update(|cx| {
11302        cx.update_global(|store: &mut SettingsStore, cx| {
11303            store.update_user_settings::<ProjectSettings>(cx, f);
11304        });
11305    });
11306}
11307
11308pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
11309    _ = cx.update(|cx| {
11310        let store = SettingsStore::test(cx);
11311        cx.set_global(store);
11312        theme::init(theme::LoadThemes::JustBase, cx);
11313        release_channel::init("0.0.0", cx);
11314        client::init_settings(cx);
11315        language::init(cx);
11316        Project::init_settings(cx);
11317        workspace::init_settings(cx);
11318        crate::init(cx);
11319    });
11320
11321    update_test_language_settings(cx, f);
11322}
11323
11324pub(crate) fn rust_lang() -> Arc<Language> {
11325    Arc::new(Language::new(
11326        LanguageConfig {
11327            name: "Rust".into(),
11328            matcher: LanguageMatcher {
11329                path_suffixes: vec!["rs".to_string()],
11330                ..Default::default()
11331            },
11332            ..Default::default()
11333        },
11334        Some(tree_sitter_rust::language()),
11335    ))
11336}
11337
11338#[track_caller]
11339fn assert_hunk_revert(
11340    not_reverted_text_with_selections: &str,
11341    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
11342    expected_reverted_text_with_selections: &str,
11343    base_text: &str,
11344    cx: &mut EditorLspTestContext,
11345) {
11346    cx.set_state(not_reverted_text_with_selections);
11347    cx.update_editor(|editor, cx| {
11348        editor
11349            .buffer()
11350            .read(cx)
11351            .as_singleton()
11352            .unwrap()
11353            .update(cx, |buffer, cx| {
11354                buffer.set_diff_base(Some(base_text.into()), cx);
11355            });
11356    });
11357    cx.executor().run_until_parked();
11358
11359    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
11360        let snapshot = editor
11361            .buffer()
11362            .read(cx)
11363            .as_singleton()
11364            .unwrap()
11365            .read(cx)
11366            .snapshot();
11367        let reverted_hunk_statuses = snapshot
11368            .git_diff_hunks_in_row_range(0..u32::MAX)
11369            .map(|hunk| hunk.status())
11370            .collect::<Vec<_>>();
11371
11372        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11373        reverted_hunk_statuses
11374    });
11375    cx.executor().run_until_parked();
11376    cx.assert_editor_state(expected_reverted_text_with_selections);
11377    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
11378}