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
 3243#[gpui::test]
 3244fn test_duplicate_line(cx: &mut TestAppContext) {
 3245    init_test(cx, |_| {});
 3246
 3247    let view = cx.add_window(|cx| {
 3248        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3249        build_editor(buffer, cx)
 3250    });
 3251    _ = view.update(cx, |view, cx| {
 3252        view.change_selections(None, cx, |s| {
 3253            s.select_display_ranges([
 3254                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3255                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3256                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3257                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 3258            ])
 3259        });
 3260        view.duplicate_line_down(&DuplicateLineDown, cx);
 3261        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 3262        assert_eq!(
 3263            view.selections.display_ranges(cx),
 3264            vec![
 3265                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 3266                DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
 3267                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 3268                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
 3269            ]
 3270        );
 3271    });
 3272
 3273    let view = cx.add_window(|cx| {
 3274        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3275        build_editor(buffer, cx)
 3276    });
 3277    _ = view.update(cx, |view, cx| {
 3278        view.change_selections(None, cx, |s| {
 3279            s.select_display_ranges([
 3280                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
 3281                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
 3282            ])
 3283        });
 3284        view.duplicate_line_down(&DuplicateLineDown, cx);
 3285        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 3286        assert_eq!(
 3287            view.selections.display_ranges(cx),
 3288            vec![
 3289                DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
 3290                DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
 3291            ]
 3292        );
 3293    });
 3294
 3295    // With `move_upwards` the selections stay in place, except for
 3296    // the lines inserted above them
 3297    let view = cx.add_window(|cx| {
 3298        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3299        build_editor(buffer, cx)
 3300    });
 3301    _ = view.update(cx, |view, cx| {
 3302        view.change_selections(None, cx, |s| {
 3303            s.select_display_ranges([
 3304                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3305                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3306                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3307                DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
 3308            ])
 3309        });
 3310        view.duplicate_line_up(&DuplicateLineUp, cx);
 3311        assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 3312        assert_eq!(
 3313            view.selections.display_ranges(cx),
 3314            vec![
 3315                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3316                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3317                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
 3318                DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
 3319            ]
 3320        );
 3321    });
 3322
 3323    let view = cx.add_window(|cx| {
 3324        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3325        build_editor(buffer, cx)
 3326    });
 3327    _ = view.update(cx, |view, cx| {
 3328        view.change_selections(None, cx, |s| {
 3329            s.select_display_ranges([
 3330                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
 3331                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
 3332            ])
 3333        });
 3334        view.duplicate_line_up(&DuplicateLineUp, cx);
 3335        assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 3336        assert_eq!(
 3337            view.selections.display_ranges(cx),
 3338            vec![
 3339                DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
 3340                DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
 3341            ]
 3342        );
 3343    });
 3344}
 3345
 3346#[gpui::test]
 3347fn test_move_line_up_down(cx: &mut TestAppContext) {
 3348    init_test(cx, |_| {});
 3349
 3350    let view = cx.add_window(|cx| {
 3351        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 3352        build_editor(buffer, cx)
 3353    });
 3354    _ = view.update(cx, |view, cx| {
 3355        view.fold_ranges(
 3356            vec![
 3357                Point::new(0, 2)..Point::new(1, 2),
 3358                Point::new(2, 3)..Point::new(4, 1),
 3359                Point::new(7, 0)..Point::new(8, 4),
 3360            ],
 3361            true,
 3362            cx,
 3363        );
 3364        view.change_selections(None, cx, |s| {
 3365            s.select_display_ranges([
 3366                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 3367                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
 3368                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
 3369                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
 3370            ])
 3371        });
 3372        assert_eq!(
 3373            view.display_text(cx),
 3374            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 3375        );
 3376
 3377        view.move_line_up(&MoveLineUp, cx);
 3378        assert_eq!(
 3379            view.display_text(cx),
 3380            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 3381        );
 3382        assert_eq!(
 3383            view.selections.display_ranges(cx),
 3384            vec![
 3385                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 3386                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
 3387                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
 3388                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
 3389            ]
 3390        );
 3391    });
 3392
 3393    _ = view.update(cx, |view, cx| {
 3394        view.move_line_down(&MoveLineDown, cx);
 3395        assert_eq!(
 3396            view.display_text(cx),
 3397            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 3398        );
 3399        assert_eq!(
 3400            view.selections.display_ranges(cx),
 3401            vec![
 3402                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 3403                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
 3404                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
 3405                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
 3406            ]
 3407        );
 3408    });
 3409
 3410    _ = view.update(cx, |view, cx| {
 3411        view.move_line_down(&MoveLineDown, cx);
 3412        assert_eq!(
 3413            view.display_text(cx),
 3414            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 3415        );
 3416        assert_eq!(
 3417            view.selections.display_ranges(cx),
 3418            vec![
 3419                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
 3420                DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
 3421                DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
 3422                DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
 3423            ]
 3424        );
 3425    });
 3426
 3427    _ = view.update(cx, |view, cx| {
 3428        view.move_line_up(&MoveLineUp, cx);
 3429        assert_eq!(
 3430            view.display_text(cx),
 3431            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 3432        );
 3433        assert_eq!(
 3434            view.selections.display_ranges(cx),
 3435            vec![
 3436                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 3437                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
 3438                DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
 3439                DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
 3440            ]
 3441        );
 3442    });
 3443}
 3444
 3445#[gpui::test]
 3446fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 3447    init_test(cx, |_| {});
 3448
 3449    let editor = cx.add_window(|cx| {
 3450        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 3451        build_editor(buffer, cx)
 3452    });
 3453    _ = editor.update(cx, |editor, cx| {
 3454        let snapshot = editor.buffer.read(cx).snapshot(cx);
 3455        editor.insert_blocks(
 3456            [BlockProperties {
 3457                style: BlockStyle::Fixed,
 3458                position: snapshot.anchor_after(Point::new(2, 0)),
 3459                disposition: BlockDisposition::Below,
 3460                height: 1,
 3461                render: Box::new(|_| div().into_any()),
 3462            }],
 3463            Some(Autoscroll::fit()),
 3464            cx,
 3465        );
 3466        editor.change_selections(None, cx, |s| {
 3467            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 3468        });
 3469        editor.move_line_down(&MoveLineDown, cx);
 3470    });
 3471}
 3472
 3473#[gpui::test]
 3474fn test_transpose(cx: &mut TestAppContext) {
 3475    init_test(cx, |_| {});
 3476
 3477    _ = cx.add_window(|cx| {
 3478        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
 3479        editor.set_style(EditorStyle::default(), cx);
 3480        editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
 3481        editor.transpose(&Default::default(), cx);
 3482        assert_eq!(editor.text(cx), "bac");
 3483        assert_eq!(editor.selections.ranges(cx), [2..2]);
 3484
 3485        editor.transpose(&Default::default(), cx);
 3486        assert_eq!(editor.text(cx), "bca");
 3487        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3488
 3489        editor.transpose(&Default::default(), cx);
 3490        assert_eq!(editor.text(cx), "bac");
 3491        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3492
 3493        editor
 3494    });
 3495
 3496    _ = cx.add_window(|cx| {
 3497        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 3498        editor.set_style(EditorStyle::default(), cx);
 3499        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
 3500        editor.transpose(&Default::default(), cx);
 3501        assert_eq!(editor.text(cx), "acb\nde");
 3502        assert_eq!(editor.selections.ranges(cx), [3..3]);
 3503
 3504        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
 3505        editor.transpose(&Default::default(), cx);
 3506        assert_eq!(editor.text(cx), "acbd\ne");
 3507        assert_eq!(editor.selections.ranges(cx), [5..5]);
 3508
 3509        editor.transpose(&Default::default(), cx);
 3510        assert_eq!(editor.text(cx), "acbde\n");
 3511        assert_eq!(editor.selections.ranges(cx), [6..6]);
 3512
 3513        editor.transpose(&Default::default(), cx);
 3514        assert_eq!(editor.text(cx), "acbd\ne");
 3515        assert_eq!(editor.selections.ranges(cx), [6..6]);
 3516
 3517        editor
 3518    });
 3519
 3520    _ = cx.add_window(|cx| {
 3521        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 3522        editor.set_style(EditorStyle::default(), cx);
 3523        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 3524        editor.transpose(&Default::default(), cx);
 3525        assert_eq!(editor.text(cx), "bacd\ne");
 3526        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 3527
 3528        editor.transpose(&Default::default(), cx);
 3529        assert_eq!(editor.text(cx), "bcade\n");
 3530        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 3531
 3532        editor.transpose(&Default::default(), cx);
 3533        assert_eq!(editor.text(cx), "bcda\ne");
 3534        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 3535
 3536        editor.transpose(&Default::default(), cx);
 3537        assert_eq!(editor.text(cx), "bcade\n");
 3538        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 3539
 3540        editor.transpose(&Default::default(), cx);
 3541        assert_eq!(editor.text(cx), "bcaed\n");
 3542        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 3543
 3544        editor
 3545    });
 3546
 3547    _ = cx.add_window(|cx| {
 3548        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
 3549        editor.set_style(EditorStyle::default(), cx);
 3550        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
 3551        editor.transpose(&Default::default(), cx);
 3552        assert_eq!(editor.text(cx), "🏀🍐✋");
 3553        assert_eq!(editor.selections.ranges(cx), [8..8]);
 3554
 3555        editor.transpose(&Default::default(), cx);
 3556        assert_eq!(editor.text(cx), "🏀✋🍐");
 3557        assert_eq!(editor.selections.ranges(cx), [11..11]);
 3558
 3559        editor.transpose(&Default::default(), cx);
 3560        assert_eq!(editor.text(cx), "🏀🍐✋");
 3561        assert_eq!(editor.selections.ranges(cx), [11..11]);
 3562
 3563        editor
 3564    });
 3565}
 3566
 3567#[gpui::test]
 3568async fn test_clipboard(cx: &mut gpui::TestAppContext) {
 3569    init_test(cx, |_| {});
 3570
 3571    let mut cx = EditorTestContext::new(cx).await;
 3572
 3573    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 3574    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3575    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 3576
 3577    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 3578    cx.set_state("two ˇfour ˇsix ˇ");
 3579    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3580    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 3581
 3582    // Paste again but with only two cursors. Since the number of cursors doesn't
 3583    // match the number of slices in the clipboard, the entire clipboard text
 3584    // is pasted at each cursor.
 3585    cx.set_state("ˇtwo one✅ four three six five ˇ");
 3586    cx.update_editor(|e, cx| {
 3587        e.handle_input("( ", cx);
 3588        e.paste(&Paste, cx);
 3589        e.handle_input(") ", cx);
 3590    });
 3591    cx.assert_editor_state(
 3592        &([
 3593            "( one✅ ",
 3594            "three ",
 3595            "five ) ˇtwo one✅ four three six five ( one✅ ",
 3596            "three ",
 3597            "five ) ˇ",
 3598        ]
 3599        .join("\n")),
 3600    );
 3601
 3602    // Cut with three selections, one of which is full-line.
 3603    cx.set_state(indoc! {"
 3604        1«2ˇ»3
 3605        4ˇ567
 3606        «8ˇ»9"});
 3607    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3608    cx.assert_editor_state(indoc! {"
 3609        1ˇ3
 3610        ˇ9"});
 3611
 3612    // Paste with three selections, noticing how the copied selection that was full-line
 3613    // gets inserted before the second cursor.
 3614    cx.set_state(indoc! {"
 3615        1ˇ3
 3616 3617        «oˇ»ne"});
 3618    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3619    cx.assert_editor_state(indoc! {"
 3620        12ˇ3
 3621        4567
 3622 3623        8ˇne"});
 3624
 3625    // Copy with a single cursor only, which writes the whole line into the clipboard.
 3626    cx.set_state(indoc! {"
 3627        The quick brown
 3628        fox juˇmps over
 3629        the lazy dog"});
 3630    cx.update_editor(|e, cx| e.copy(&Copy, cx));
 3631    assert_eq!(
 3632        cx.read_from_clipboard().map(|item| item.text().to_owned()),
 3633        Some("fox jumps over\n".to_owned())
 3634    );
 3635
 3636    // Paste with three selections, noticing how the copied full-line selection is inserted
 3637    // before the empty selections but replaces the selection that is non-empty.
 3638    cx.set_state(indoc! {"
 3639        Tˇhe quick brown
 3640        «foˇ»x jumps over
 3641        tˇhe lazy dog"});
 3642    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3643    cx.assert_editor_state(indoc! {"
 3644        fox jumps over
 3645        Tˇhe quick brown
 3646        fox jumps over
 3647        ˇx jumps over
 3648        fox jumps over
 3649        tˇhe lazy dog"});
 3650}
 3651
 3652#[gpui::test]
 3653async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 3654    init_test(cx, |_| {});
 3655
 3656    let mut cx = EditorTestContext::new(cx).await;
 3657    let language = Arc::new(Language::new(
 3658        LanguageConfig::default(),
 3659        Some(tree_sitter_rust::language()),
 3660    ));
 3661    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3662
 3663    // Cut an indented block, without the leading whitespace.
 3664    cx.set_state(indoc! {"
 3665        const a: B = (
 3666            c(),
 3667            «d(
 3668                e,
 3669                f
 3670            )ˇ»
 3671        );
 3672    "});
 3673    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3674    cx.assert_editor_state(indoc! {"
 3675        const a: B = (
 3676            c(),
 3677            ˇ
 3678        );
 3679    "});
 3680
 3681    // Paste it at the same position.
 3682    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3683    cx.assert_editor_state(indoc! {"
 3684        const a: B = (
 3685            c(),
 3686            d(
 3687                e,
 3688                f
 3689 3690        );
 3691    "});
 3692
 3693    // Paste it at a line with a lower indent level.
 3694    cx.set_state(indoc! {"
 3695        ˇ
 3696        const a: B = (
 3697            c(),
 3698        );
 3699    "});
 3700    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        d(
 3703            e,
 3704            f
 3705 3706        const a: B = (
 3707            c(),
 3708        );
 3709    "});
 3710
 3711    // Cut an indented block, with the leading whitespace.
 3712    cx.set_state(indoc! {"
 3713        const a: B = (
 3714            c(),
 3715        «    d(
 3716                e,
 3717                f
 3718            )
 3719        ˇ»);
 3720    "});
 3721    cx.update_editor(|e, cx| e.cut(&Cut, cx));
 3722    cx.assert_editor_state(indoc! {"
 3723        const a: B = (
 3724            c(),
 3725        ˇ);
 3726    "});
 3727
 3728    // Paste it at the same position.
 3729    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3730    cx.assert_editor_state(indoc! {"
 3731        const a: B = (
 3732            c(),
 3733            d(
 3734                e,
 3735                f
 3736            )
 3737        ˇ);
 3738    "});
 3739
 3740    // Paste it at a line with a higher indent level.
 3741    cx.set_state(indoc! {"
 3742        const a: B = (
 3743            c(),
 3744            d(
 3745                e,
 3746 3747            )
 3748        );
 3749    "});
 3750    cx.update_editor(|e, cx| e.paste(&Paste, cx));
 3751    cx.assert_editor_state(indoc! {"
 3752        const a: B = (
 3753            c(),
 3754            d(
 3755                e,
 3756                f    d(
 3757                    e,
 3758                    f
 3759                )
 3760        ˇ
 3761            )
 3762        );
 3763    "});
 3764}
 3765
 3766#[gpui::test]
 3767fn test_select_all(cx: &mut TestAppContext) {
 3768    init_test(cx, |_| {});
 3769
 3770    let view = cx.add_window(|cx| {
 3771        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 3772        build_editor(buffer, cx)
 3773    });
 3774    _ = view.update(cx, |view, cx| {
 3775        view.select_all(&SelectAll, cx);
 3776        assert_eq!(
 3777            view.selections.display_ranges(cx),
 3778            &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
 3779        );
 3780    });
 3781}
 3782
 3783#[gpui::test]
 3784fn test_select_line(cx: &mut TestAppContext) {
 3785    init_test(cx, |_| {});
 3786
 3787    let view = cx.add_window(|cx| {
 3788        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 3789        build_editor(buffer, cx)
 3790    });
 3791    _ = view.update(cx, |view, cx| {
 3792        view.change_selections(None, cx, |s| {
 3793            s.select_display_ranges([
 3794                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3795                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3796                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3797                DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
 3798            ])
 3799        });
 3800        view.select_line(&SelectLine, cx);
 3801        assert_eq!(
 3802            view.selections.display_ranges(cx),
 3803            vec![
 3804                DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
 3805                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
 3806            ]
 3807        );
 3808    });
 3809
 3810    _ = view.update(cx, |view, cx| {
 3811        view.select_line(&SelectLine, cx);
 3812        assert_eq!(
 3813            view.selections.display_ranges(cx),
 3814            vec![
 3815                DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
 3816                DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
 3817            ]
 3818        );
 3819    });
 3820
 3821    _ = view.update(cx, |view, cx| {
 3822        view.select_line(&SelectLine, cx);
 3823        assert_eq!(
 3824            view.selections.display_ranges(cx),
 3825            vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
 3826        );
 3827    });
 3828}
 3829
 3830#[gpui::test]
 3831fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 3832    init_test(cx, |_| {});
 3833
 3834    let view = cx.add_window(|cx| {
 3835        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 3836        build_editor(buffer, cx)
 3837    });
 3838    _ = view.update(cx, |view, cx| {
 3839        view.fold_ranges(
 3840            vec![
 3841                Point::new(0, 2)..Point::new(1, 2),
 3842                Point::new(2, 3)..Point::new(4, 1),
 3843                Point::new(7, 0)..Point::new(8, 4),
 3844            ],
 3845            true,
 3846            cx,
 3847        );
 3848        view.change_selections(None, cx, |s| {
 3849            s.select_display_ranges([
 3850                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 3851                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3852                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
 3853                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
 3854            ])
 3855        });
 3856        assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
 3857    });
 3858
 3859    _ = view.update(cx, |view, cx| {
 3860        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
 3861        assert_eq!(
 3862            view.display_text(cx),
 3863            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 3864        );
 3865        assert_eq!(
 3866            view.selections.display_ranges(cx),
 3867            [
 3868                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 3869                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
 3870                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
 3871                DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
 3872            ]
 3873        );
 3874    });
 3875
 3876    _ = view.update(cx, |view, cx| {
 3877        view.change_selections(None, cx, |s| {
 3878            s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
 3879        });
 3880        view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
 3881        assert_eq!(
 3882            view.display_text(cx),
 3883            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 3884        );
 3885        assert_eq!(
 3886            view.selections.display_ranges(cx),
 3887            [
 3888                DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
 3889                DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
 3890                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
 3891                DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
 3892                DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
 3893                DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
 3894                DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
 3895                DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
 3896            ]
 3897        );
 3898    });
 3899}
 3900
 3901#[gpui::test]
 3902async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 3903    init_test(cx, |_| {});
 3904
 3905    let mut cx = EditorTestContext::new(cx).await;
 3906
 3907    // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
 3908    cx.set_state(indoc!(
 3909        r#"abc
 3910           defˇghi
 3911
 3912           jk
 3913           nlmo
 3914           "#
 3915    ));
 3916
 3917    cx.update_editor(|editor, cx| {
 3918        editor.add_selection_above(&Default::default(), cx);
 3919    });
 3920
 3921    cx.assert_editor_state(indoc!(
 3922        r#"abcˇ
 3923           defˇghi
 3924
 3925           jk
 3926           nlmo
 3927           "#
 3928    ));
 3929
 3930    cx.update_editor(|editor, cx| {
 3931        editor.add_selection_above(&Default::default(), cx);
 3932    });
 3933
 3934    cx.assert_editor_state(indoc!(
 3935        r#"abcˇ
 3936            defˇghi
 3937
 3938            jk
 3939            nlmo
 3940            "#
 3941    ));
 3942
 3943    cx.update_editor(|view, cx| {
 3944        view.add_selection_below(&Default::default(), cx);
 3945    });
 3946
 3947    cx.assert_editor_state(indoc!(
 3948        r#"abc
 3949           defˇghi
 3950
 3951           jk
 3952           nlmo
 3953           "#
 3954    ));
 3955
 3956    cx.update_editor(|view, cx| {
 3957        view.undo_selection(&Default::default(), cx);
 3958    });
 3959
 3960    cx.assert_editor_state(indoc!(
 3961        r#"abcˇ
 3962           defˇghi
 3963
 3964           jk
 3965           nlmo
 3966           "#
 3967    ));
 3968
 3969    cx.update_editor(|view, cx| {
 3970        view.redo_selection(&Default::default(), cx);
 3971    });
 3972
 3973    cx.assert_editor_state(indoc!(
 3974        r#"abc
 3975           defˇghi
 3976
 3977           jk
 3978           nlmo
 3979           "#
 3980    ));
 3981
 3982    cx.update_editor(|view, cx| {
 3983        view.add_selection_below(&Default::default(), cx);
 3984    });
 3985
 3986    cx.assert_editor_state(indoc!(
 3987        r#"abc
 3988           defˇghi
 3989
 3990           jk
 3991           nlmˇo
 3992           "#
 3993    ));
 3994
 3995    cx.update_editor(|view, cx| {
 3996        view.add_selection_below(&Default::default(), cx);
 3997    });
 3998
 3999    cx.assert_editor_state(indoc!(
 4000        r#"abc
 4001           defˇghi
 4002
 4003           jk
 4004           nlmˇo
 4005           "#
 4006    ));
 4007
 4008    // change selections
 4009    cx.set_state(indoc!(
 4010        r#"abc
 4011           def«ˇg»hi
 4012
 4013           jk
 4014           nlmo
 4015           "#
 4016    ));
 4017
 4018    cx.update_editor(|view, cx| {
 4019        view.add_selection_below(&Default::default(), cx);
 4020    });
 4021
 4022    cx.assert_editor_state(indoc!(
 4023        r#"abc
 4024           def«ˇg»hi
 4025
 4026           jk
 4027           nlm«ˇo»
 4028           "#
 4029    ));
 4030
 4031    cx.update_editor(|view, cx| {
 4032        view.add_selection_below(&Default::default(), cx);
 4033    });
 4034
 4035    cx.assert_editor_state(indoc!(
 4036        r#"abc
 4037           def«ˇg»hi
 4038
 4039           jk
 4040           nlm«ˇo»
 4041           "#
 4042    ));
 4043
 4044    cx.update_editor(|view, cx| {
 4045        view.add_selection_above(&Default::default(), cx);
 4046    });
 4047
 4048    cx.assert_editor_state(indoc!(
 4049        r#"abc
 4050           def«ˇg»hi
 4051
 4052           jk
 4053           nlmo
 4054           "#
 4055    ));
 4056
 4057    cx.update_editor(|view, cx| {
 4058        view.add_selection_above(&Default::default(), cx);
 4059    });
 4060
 4061    cx.assert_editor_state(indoc!(
 4062        r#"abc
 4063           def«ˇg»hi
 4064
 4065           jk
 4066           nlmo
 4067           "#
 4068    ));
 4069
 4070    // Change selections again
 4071    cx.set_state(indoc!(
 4072        r#"a«bc
 4073           defgˇ»hi
 4074
 4075           jk
 4076           nlmo
 4077           "#
 4078    ));
 4079
 4080    cx.update_editor(|view, cx| {
 4081        view.add_selection_below(&Default::default(), cx);
 4082    });
 4083
 4084    cx.assert_editor_state(indoc!(
 4085        r#"a«bcˇ»
 4086           d«efgˇ»hi
 4087
 4088           j«kˇ»
 4089           nlmo
 4090           "#
 4091    ));
 4092
 4093    cx.update_editor(|view, cx| {
 4094        view.add_selection_below(&Default::default(), cx);
 4095    });
 4096    cx.assert_editor_state(indoc!(
 4097        r#"a«bcˇ»
 4098           d«efgˇ»hi
 4099
 4100           j«kˇ»
 4101           n«lmoˇ»
 4102           "#
 4103    ));
 4104    cx.update_editor(|view, cx| {
 4105        view.add_selection_above(&Default::default(), cx);
 4106    });
 4107
 4108    cx.assert_editor_state(indoc!(
 4109        r#"a«bcˇ»
 4110           d«efgˇ»hi
 4111
 4112           j«kˇ»
 4113           nlmo
 4114           "#
 4115    ));
 4116
 4117    // Change selections again
 4118    cx.set_state(indoc!(
 4119        r#"abc
 4120           d«ˇefghi
 4121
 4122           jk
 4123           nlm»o
 4124           "#
 4125    ));
 4126
 4127    cx.update_editor(|view, cx| {
 4128        view.add_selection_above(&Default::default(), cx);
 4129    });
 4130
 4131    cx.assert_editor_state(indoc!(
 4132        r#"a«ˇbc»
 4133           d«ˇef»ghi
 4134
 4135           j«ˇk»
 4136           n«ˇlm»o
 4137           "#
 4138    ));
 4139
 4140    cx.update_editor(|view, cx| {
 4141        view.add_selection_below(&Default::default(), cx);
 4142    });
 4143
 4144    cx.assert_editor_state(indoc!(
 4145        r#"abc
 4146           d«ˇef»ghi
 4147
 4148           j«ˇk»
 4149           n«ˇlm»o
 4150           "#
 4151    ));
 4152}
 4153
 4154#[gpui::test]
 4155async fn test_select_next(cx: &mut gpui::TestAppContext) {
 4156    init_test(cx, |_| {});
 4157
 4158    let mut cx = EditorTestContext::new(cx).await;
 4159    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4160
 4161    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4162        .unwrap();
 4163    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4164
 4165    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4166        .unwrap();
 4167    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 4168
 4169    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4170    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4171
 4172    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4173    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 4174
 4175    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4176        .unwrap();
 4177    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4178
 4179    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4180        .unwrap();
 4181    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4182}
 4183
 4184#[gpui::test]
 4185async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
 4186    init_test(cx, |_| {});
 4187
 4188    let mut cx = EditorTestContext::new(cx).await;
 4189    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4190
 4191    cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
 4192        .unwrap();
 4193    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
 4198    init_test(cx, |_| {});
 4199
 4200    let mut cx = EditorTestContext::new(cx).await;
 4201    cx.set_state(
 4202        r#"let foo = 2;
 4203lˇet foo = 2;
 4204let fooˇ = 2;
 4205let foo = 2;
 4206let foo = ˇ2;"#,
 4207    );
 4208
 4209    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4210        .unwrap();
 4211    cx.assert_editor_state(
 4212        r#"let foo = 2;
 4213«letˇ» foo = 2;
 4214let «fooˇ» = 2;
 4215let foo = 2;
 4216let foo = «2ˇ»;"#,
 4217    );
 4218
 4219    // noop for multiple selections with different contents
 4220    cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
 4221        .unwrap();
 4222    cx.assert_editor_state(
 4223        r#"let foo = 2;
 4224«letˇ» foo = 2;
 4225let «fooˇ» = 2;
 4226let foo = 2;
 4227let foo = «2ˇ»;"#,
 4228    );
 4229}
 4230
 4231#[gpui::test]
 4232async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
 4233    init_test(cx, |_| {});
 4234
 4235    let mut cx = EditorTestContext::new_multibuffer(
 4236        cx,
 4237        [
 4238            indoc! {
 4239                "aaa\n«bbb\nccc\n»ddd"
 4240            },
 4241            indoc! {
 4242                "aaa\n«bbb\nccc\n»ddd"
 4243            },
 4244        ],
 4245    );
 4246
 4247    cx.assert_editor_state(indoc! {"
 4248        ˇbbb
 4249        ccc
 4250
 4251        bbb
 4252        ccc
 4253        "});
 4254    cx.dispatch_action(SelectPrevious::default());
 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}
 4271
 4272#[gpui::test]
 4273async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
 4274    init_test(cx, |_| {});
 4275
 4276    let mut cx = EditorTestContext::new(cx).await;
 4277    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 4278
 4279    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4280        .unwrap();
 4281    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4282
 4283    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4284        .unwrap();
 4285    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 4286
 4287    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4288    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 4289
 4290    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4291    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 4292
 4293    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4294        .unwrap();
 4295    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 4296
 4297    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4298        .unwrap();
 4299    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 4300
 4301    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4302        .unwrap();
 4303    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 4304}
 4305
 4306#[gpui::test]
 4307async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
 4308    init_test(cx, |_| {});
 4309
 4310    let mut cx = EditorTestContext::new(cx).await;
 4311    cx.set_state(
 4312        r#"let foo = 2;
 4313lˇet foo = 2;
 4314let fooˇ = 2;
 4315let foo = 2;
 4316let foo = ˇ2;"#,
 4317    );
 4318
 4319    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4320        .unwrap();
 4321    cx.assert_editor_state(
 4322        r#"let foo = 2;
 4323«letˇ» foo = 2;
 4324let «fooˇ» = 2;
 4325let foo = 2;
 4326let foo = «2ˇ»;"#,
 4327    );
 4328
 4329    // noop for multiple selections with different contents
 4330    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4331        .unwrap();
 4332    cx.assert_editor_state(
 4333        r#"let foo = 2;
 4334«letˇ» foo = 2;
 4335let «fooˇ» = 2;
 4336let foo = 2;
 4337let foo = «2ˇ»;"#,
 4338    );
 4339}
 4340
 4341#[gpui::test]
 4342async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
 4343    init_test(cx, |_| {});
 4344
 4345    let mut cx = EditorTestContext::new(cx).await;
 4346    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 4347
 4348    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4349        .unwrap();
 4350    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 4351
 4352    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4353        .unwrap();
 4354    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 4355
 4356    cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
 4357    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 4358
 4359    cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
 4360    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 4361
 4362    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4363        .unwrap();
 4364    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 4365
 4366    cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
 4367        .unwrap();
 4368    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 4369}
 4370
 4371#[gpui::test]
 4372async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
 4373    init_test(cx, |_| {});
 4374
 4375    let language = Arc::new(Language::new(
 4376        LanguageConfig::default(),
 4377        Some(tree_sitter_rust::language()),
 4378    ));
 4379
 4380    let text = r#"
 4381        use mod1::mod2::{mod3, mod4};
 4382
 4383        fn fn_1(param1: bool, param2: &str) {
 4384            let var1 = "text";
 4385        }
 4386    "#
 4387    .unindent();
 4388
 4389    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 4390    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 4391    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 4392
 4393    view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 4394        .await;
 4395
 4396    _ = view.update(cx, |view, cx| {
 4397        view.change_selections(None, cx, |s| {
 4398            s.select_display_ranges([
 4399                DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
 4400                DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
 4401                DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
 4402            ]);
 4403        });
 4404        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4405    });
 4406    assert_eq!(
 4407        view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
 4408        &[
 4409            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
 4410            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
 4411            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
 4412        ]
 4413    );
 4414
 4415    _ = view.update(cx, |view, cx| {
 4416        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4417    });
 4418    assert_eq!(
 4419        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4420        &[
 4421            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
 4422            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
 4423        ]
 4424    );
 4425
 4426    _ = view.update(cx, |view, cx| {
 4427        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4428    });
 4429    assert_eq!(
 4430        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4431        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
 4432    );
 4433
 4434    // Trying to expand the selected syntax node one more time has no effect.
 4435    _ = view.update(cx, |view, cx| {
 4436        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4437    });
 4438    assert_eq!(
 4439        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4440        &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
 4441    );
 4442
 4443    _ = view.update(cx, |view, cx| {
 4444        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4445    });
 4446    assert_eq!(
 4447        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4448        &[
 4449            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
 4450            DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
 4451        ]
 4452    );
 4453
 4454    _ = view.update(cx, |view, cx| {
 4455        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4456    });
 4457    assert_eq!(
 4458        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4459        &[
 4460            DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
 4461            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
 4462            DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
 4463        ]
 4464    );
 4465
 4466    _ = view.update(cx, |view, cx| {
 4467        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4468    });
 4469    assert_eq!(
 4470        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4471        &[
 4472            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
 4473            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
 4474            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
 4475        ]
 4476    );
 4477
 4478    // Trying to shrink the selected syntax node one more time has no effect.
 4479    _ = view.update(cx, |view, cx| {
 4480        view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
 4481    });
 4482    assert_eq!(
 4483        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4484        &[
 4485            DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
 4486            DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
 4487            DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
 4488        ]
 4489    );
 4490
 4491    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 4492    // a fold.
 4493    _ = view.update(cx, |view, cx| {
 4494        view.fold_ranges(
 4495            vec![
 4496                Point::new(0, 21)..Point::new(0, 24),
 4497                Point::new(3, 20)..Point::new(3, 22),
 4498            ],
 4499            true,
 4500            cx,
 4501        );
 4502        view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
 4503    });
 4504    assert_eq!(
 4505        view.update(cx, |view, cx| view.selections.display_ranges(cx)),
 4506        &[
 4507            DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
 4508            DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
 4509            DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
 4510        ]
 4511    );
 4512}
 4513
 4514#[gpui::test]
 4515async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 4516    init_test(cx, |_| {});
 4517
 4518    let language = Arc::new(
 4519        Language::new(
 4520            LanguageConfig {
 4521                brackets: BracketPairConfig {
 4522                    pairs: vec![
 4523                        BracketPair {
 4524                            start: "{".to_string(),
 4525                            end: "}".to_string(),
 4526                            close: false,
 4527                            newline: true,
 4528                        },
 4529                        BracketPair {
 4530                            start: "(".to_string(),
 4531                            end: ")".to_string(),
 4532                            close: false,
 4533                            newline: true,
 4534                        },
 4535                    ],
 4536                    ..Default::default()
 4537                },
 4538                ..Default::default()
 4539            },
 4540            Some(tree_sitter_rust::language()),
 4541        )
 4542        .with_indents_query(
 4543            r#"
 4544                (_ "(" ")" @end) @indent
 4545                (_ "{" "}" @end) @indent
 4546            "#,
 4547        )
 4548        .unwrap(),
 4549    );
 4550
 4551    let text = "fn a() {}";
 4552
 4553    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 4554    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 4555    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 4556    editor
 4557        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 4558        .await;
 4559
 4560    _ = editor.update(cx, |editor, cx| {
 4561        editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 4562        editor.newline(&Newline, cx);
 4563        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 4564        assert_eq!(
 4565            editor.selections.ranges(cx),
 4566            &[
 4567                Point::new(1, 4)..Point::new(1, 4),
 4568                Point::new(3, 4)..Point::new(3, 4),
 4569                Point::new(5, 0)..Point::new(5, 0)
 4570            ]
 4571        );
 4572    });
 4573}
 4574
 4575#[gpui::test]
 4576async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
 4577    init_test(cx, |_| {});
 4578
 4579    let mut cx = EditorTestContext::new(cx).await;
 4580
 4581    let language = Arc::new(Language::new(
 4582        LanguageConfig {
 4583            brackets: BracketPairConfig {
 4584                pairs: vec![
 4585                    BracketPair {
 4586                        start: "{".to_string(),
 4587                        end: "}".to_string(),
 4588                        close: true,
 4589                        newline: true,
 4590                    },
 4591                    BracketPair {
 4592                        start: "(".to_string(),
 4593                        end: ")".to_string(),
 4594                        close: true,
 4595                        newline: true,
 4596                    },
 4597                    BracketPair {
 4598                        start: "/*".to_string(),
 4599                        end: " */".to_string(),
 4600                        close: true,
 4601                        newline: true,
 4602                    },
 4603                    BracketPair {
 4604                        start: "[".to_string(),
 4605                        end: "]".to_string(),
 4606                        close: false,
 4607                        newline: true,
 4608                    },
 4609                    BracketPair {
 4610                        start: "\"".to_string(),
 4611                        end: "\"".to_string(),
 4612                        close: true,
 4613                        newline: false,
 4614                    },
 4615                ],
 4616                ..Default::default()
 4617            },
 4618            autoclose_before: "})]".to_string(),
 4619            ..Default::default()
 4620        },
 4621        Some(tree_sitter_rust::language()),
 4622    ));
 4623
 4624    cx.language_registry().add(language.clone());
 4625    cx.update_buffer(|buffer, cx| {
 4626        buffer.set_language(Some(language), cx);
 4627    });
 4628
 4629    cx.set_state(
 4630        &r#"
 4631            🏀ˇ
 4632            εˇ
 4633            ❤️ˇ
 4634        "#
 4635        .unindent(),
 4636    );
 4637
 4638    // autoclose multiple nested brackets at multiple cursors
 4639    cx.update_editor(|view, cx| {
 4640        view.handle_input("{", cx);
 4641        view.handle_input("{", cx);
 4642        view.handle_input("{", cx);
 4643    });
 4644    cx.assert_editor_state(
 4645        &"
 4646            🏀{{{ˇ}}}
 4647            ε{{{ˇ}}}
 4648            ❤️{{{ˇ}}}
 4649        "
 4650        .unindent(),
 4651    );
 4652
 4653    // insert a different closing bracket
 4654    cx.update_editor(|view, cx| {
 4655        view.handle_input(")", cx);
 4656    });
 4657    cx.assert_editor_state(
 4658        &"
 4659            🏀{{{)ˇ}}}
 4660            ε{{{)ˇ}}}
 4661            ❤️{{{)ˇ}}}
 4662        "
 4663        .unindent(),
 4664    );
 4665
 4666    // skip over the auto-closed brackets when typing a closing bracket
 4667    cx.update_editor(|view, cx| {
 4668        view.move_right(&MoveRight, cx);
 4669        view.handle_input("}", cx);
 4670        view.handle_input("}", cx);
 4671        view.handle_input("}", cx);
 4672    });
 4673    cx.assert_editor_state(
 4674        &"
 4675            🏀{{{)}}}}ˇ
 4676            ε{{{)}}}}ˇ
 4677            ❤️{{{)}}}}ˇ
 4678        "
 4679        .unindent(),
 4680    );
 4681
 4682    // autoclose multi-character pairs
 4683    cx.set_state(
 4684        &"
 4685            ˇ
 4686            ˇ
 4687        "
 4688        .unindent(),
 4689    );
 4690    cx.update_editor(|view, cx| {
 4691        view.handle_input("/", cx);
 4692        view.handle_input("*", cx);
 4693    });
 4694    cx.assert_editor_state(
 4695        &"
 4696            /*ˇ */
 4697            /*ˇ */
 4698        "
 4699        .unindent(),
 4700    );
 4701
 4702    // one cursor autocloses a multi-character pair, one cursor
 4703    // does not autoclose.
 4704    cx.set_state(
 4705        &"
 4706 4707            ˇ
 4708        "
 4709        .unindent(),
 4710    );
 4711    cx.update_editor(|view, cx| view.handle_input("*", cx));
 4712    cx.assert_editor_state(
 4713        &"
 4714            /*ˇ */
 4715 4716        "
 4717        .unindent(),
 4718    );
 4719
 4720    // Don't autoclose if the next character isn't whitespace and isn't
 4721    // listed in the language's "autoclose_before" section.
 4722    cx.set_state("ˇa b");
 4723    cx.update_editor(|view, cx| view.handle_input("{", cx));
 4724    cx.assert_editor_state("{ˇa b");
 4725
 4726    // Don't autoclose if `close` is false for the bracket pair
 4727    cx.set_state("ˇ");
 4728    cx.update_editor(|view, cx| view.handle_input("[", cx));
 4729    cx.assert_editor_state("");
 4730
 4731    // Surround with brackets if text is selected
 4732    cx.set_state("«aˇ» b");
 4733    cx.update_editor(|view, cx| view.handle_input("{", cx));
 4734    cx.assert_editor_state("{«aˇ»} b");
 4735
 4736    // Autclose pair where the start and end characters are the same
 4737    cx.set_state("");
 4738    cx.update_editor(|view, cx| view.handle_input("\"", cx));
 4739    cx.assert_editor_state("a\"ˇ\"");
 4740    cx.update_editor(|view, cx| view.handle_input("\"", cx));
 4741    cx.assert_editor_state("a\"\"ˇ");
 4742}
 4743
 4744#[gpui::test]
 4745async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
 4746    init_test(cx, |settings| {
 4747        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 4748    });
 4749
 4750    let mut cx = EditorTestContext::new(cx).await;
 4751
 4752    let language = Arc::new(Language::new(
 4753        LanguageConfig {
 4754            brackets: BracketPairConfig {
 4755                pairs: vec![
 4756                    BracketPair {
 4757                        start: "{".to_string(),
 4758                        end: "}".to_string(),
 4759                        close: true,
 4760                        newline: true,
 4761                    },
 4762                    BracketPair {
 4763                        start: "(".to_string(),
 4764                        end: ")".to_string(),
 4765                        close: true,
 4766                        newline: true,
 4767                    },
 4768                    BracketPair {
 4769                        start: "[".to_string(),
 4770                        end: "]".to_string(),
 4771                        close: false,
 4772                        newline: true,
 4773                    },
 4774                ],
 4775                ..Default::default()
 4776            },
 4777            autoclose_before: "})]".to_string(),
 4778            ..Default::default()
 4779        },
 4780        Some(tree_sitter_rust::language()),
 4781    ));
 4782
 4783    cx.language_registry().add(language.clone());
 4784    cx.update_buffer(|buffer, cx| {
 4785        buffer.set_language(Some(language), cx);
 4786    });
 4787
 4788    cx.set_state(
 4789        &"
 4790            ˇ
 4791            ˇ
 4792            ˇ
 4793        "
 4794        .unindent(),
 4795    );
 4796
 4797    // ensure only matching closing brackets are skipped over
 4798    cx.update_editor(|view, cx| {
 4799        view.handle_input("}", cx);
 4800        view.move_left(&MoveLeft, cx);
 4801        view.handle_input(")", cx);
 4802        view.move_left(&MoveLeft, cx);
 4803    });
 4804    cx.assert_editor_state(
 4805        &"
 4806            ˇ)}
 4807            ˇ)}
 4808            ˇ)}
 4809        "
 4810        .unindent(),
 4811    );
 4812
 4813    // skip-over closing brackets at multiple cursors
 4814    cx.update_editor(|view, cx| {
 4815        view.handle_input(")", cx);
 4816        view.handle_input("}", cx);
 4817    });
 4818    cx.assert_editor_state(
 4819        &"
 4820            )}ˇ
 4821            )}ˇ
 4822            )}ˇ
 4823        "
 4824        .unindent(),
 4825    );
 4826
 4827    // ignore non-close brackets
 4828    cx.update_editor(|view, cx| {
 4829        view.handle_input("]", cx);
 4830        view.move_left(&MoveLeft, cx);
 4831        view.handle_input("]", cx);
 4832    });
 4833    cx.assert_editor_state(
 4834        &"
 4835            )}]ˇ]
 4836            )}]ˇ]
 4837            )}]ˇ]
 4838        "
 4839        .unindent(),
 4840    );
 4841}
 4842
 4843#[gpui::test]
 4844async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
 4845    init_test(cx, |_| {});
 4846
 4847    let mut cx = EditorTestContext::new(cx).await;
 4848
 4849    let html_language = Arc::new(
 4850        Language::new(
 4851            LanguageConfig {
 4852                name: "HTML".into(),
 4853                brackets: BracketPairConfig {
 4854                    pairs: vec![
 4855                        BracketPair {
 4856                            start: "<".into(),
 4857                            end: ">".into(),
 4858                            close: true,
 4859                            ..Default::default()
 4860                        },
 4861                        BracketPair {
 4862                            start: "{".into(),
 4863                            end: "}".into(),
 4864                            close: true,
 4865                            ..Default::default()
 4866                        },
 4867                        BracketPair {
 4868                            start: "(".into(),
 4869                            end: ")".into(),
 4870                            close: true,
 4871                            ..Default::default()
 4872                        },
 4873                    ],
 4874                    ..Default::default()
 4875                },
 4876                autoclose_before: "})]>".into(),
 4877                ..Default::default()
 4878            },
 4879            Some(tree_sitter_html::language()),
 4880        )
 4881        .with_injection_query(
 4882            r#"
 4883            (script_element
 4884                (raw_text) @content
 4885                (#set! "language" "javascript"))
 4886            "#,
 4887        )
 4888        .unwrap(),
 4889    );
 4890
 4891    let javascript_language = Arc::new(Language::new(
 4892        LanguageConfig {
 4893            name: "JavaScript".into(),
 4894            brackets: BracketPairConfig {
 4895                pairs: vec![
 4896                    BracketPair {
 4897                        start: "/*".into(),
 4898                        end: " */".into(),
 4899                        close: true,
 4900                        ..Default::default()
 4901                    },
 4902                    BracketPair {
 4903                        start: "{".into(),
 4904                        end: "}".into(),
 4905                        close: true,
 4906                        ..Default::default()
 4907                    },
 4908                    BracketPair {
 4909                        start: "(".into(),
 4910                        end: ")".into(),
 4911                        close: true,
 4912                        ..Default::default()
 4913                    },
 4914                ],
 4915                ..Default::default()
 4916            },
 4917            autoclose_before: "})]>".into(),
 4918            ..Default::default()
 4919        },
 4920        Some(tree_sitter_typescript::language_tsx()),
 4921    ));
 4922
 4923    cx.language_registry().add(html_language.clone());
 4924    cx.language_registry().add(javascript_language.clone());
 4925
 4926    cx.update_buffer(|buffer, cx| {
 4927        buffer.set_language(Some(html_language), cx);
 4928    });
 4929
 4930    cx.set_state(
 4931        &r#"
 4932            <body>ˇ
 4933                <script>
 4934                    var x = 1;ˇ
 4935                </script>
 4936            </body>ˇ
 4937        "#
 4938        .unindent(),
 4939    );
 4940
 4941    // Precondition: different languages are active at different locations.
 4942    cx.update_editor(|editor, cx| {
 4943        let snapshot = editor.snapshot(cx);
 4944        let cursors = editor.selections.ranges::<usize>(cx);
 4945        let languages = cursors
 4946            .iter()
 4947            .map(|c| snapshot.language_at(c.start).unwrap().name())
 4948            .collect::<Vec<_>>();
 4949        assert_eq!(
 4950            languages,
 4951            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 4952        );
 4953    });
 4954
 4955    // Angle brackets autoclose in HTML, but not JavaScript.
 4956    cx.update_editor(|editor, cx| {
 4957        editor.handle_input("<", cx);
 4958        editor.handle_input("a", cx);
 4959    });
 4960    cx.assert_editor_state(
 4961        &r#"
 4962            <body><aˇ>
 4963                <script>
 4964                    var x = 1;<aˇ
 4965                </script>
 4966            </body><aˇ>
 4967        "#
 4968        .unindent(),
 4969    );
 4970
 4971    // Curly braces and parens autoclose in both HTML and JavaScript.
 4972    cx.update_editor(|editor, cx| {
 4973        editor.handle_input(" b=", cx);
 4974        editor.handle_input("{", cx);
 4975        editor.handle_input("c", cx);
 4976        editor.handle_input("(", cx);
 4977    });
 4978    cx.assert_editor_state(
 4979        &r#"
 4980            <body><a b={c(ˇ)}>
 4981                <script>
 4982                    var x = 1;<a b={c(ˇ)}
 4983                </script>
 4984            </body><a b={c(ˇ)}>
 4985        "#
 4986        .unindent(),
 4987    );
 4988
 4989    // Brackets that were already autoclosed are skipped.
 4990    cx.update_editor(|editor, cx| {
 4991        editor.handle_input(")", cx);
 4992        editor.handle_input("d", cx);
 4993        editor.handle_input("}", cx);
 4994    });
 4995    cx.assert_editor_state(
 4996        &r#"
 4997            <body><a b={c()d}ˇ>
 4998                <script>
 4999                    var x = 1;<a b={c()d}ˇ
 5000                </script>
 5001            </body><a b={c()d}ˇ>
 5002        "#
 5003        .unindent(),
 5004    );
 5005    cx.update_editor(|editor, cx| {
 5006        editor.handle_input(">", cx);
 5007    });
 5008    cx.assert_editor_state(
 5009        &r#"
 5010            <body><a b={c()d}>ˇ
 5011                <script>
 5012                    var x = 1;<a b={c()d}>ˇ
 5013                </script>
 5014            </body><a b={c()d}>ˇ
 5015        "#
 5016        .unindent(),
 5017    );
 5018
 5019    // Reset
 5020    cx.set_state(
 5021        &r#"
 5022            <body>ˇ
 5023                <script>
 5024                    var x = 1;ˇ
 5025                </script>
 5026            </body>ˇ
 5027        "#
 5028        .unindent(),
 5029    );
 5030
 5031    cx.update_editor(|editor, cx| {
 5032        editor.handle_input("<", cx);
 5033    });
 5034    cx.assert_editor_state(
 5035        &r#"
 5036            <body><ˇ>
 5037                <script>
 5038                    var x = 1;<ˇ
 5039                </script>
 5040            </body><ˇ>
 5041        "#
 5042        .unindent(),
 5043    );
 5044
 5045    // When backspacing, the closing angle brackets are removed.
 5046    cx.update_editor(|editor, cx| {
 5047        editor.backspace(&Backspace, cx);
 5048    });
 5049    cx.assert_editor_state(
 5050        &r#"
 5051            <body>ˇ
 5052                <script>
 5053                    var x = 1;ˇ
 5054                </script>
 5055            </body>ˇ
 5056        "#
 5057        .unindent(),
 5058    );
 5059
 5060    // Block comments autoclose in JavaScript, but not HTML.
 5061    cx.update_editor(|editor, cx| {
 5062        editor.handle_input("/", cx);
 5063        editor.handle_input("*", cx);
 5064    });
 5065    cx.assert_editor_state(
 5066        &r#"
 5067            <body>/*ˇ
 5068                <script>
 5069                    var x = 1;/*ˇ */
 5070                </script>
 5071            </body>/*ˇ
 5072        "#
 5073        .unindent(),
 5074    );
 5075}
 5076
 5077#[gpui::test]
 5078async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
 5079    init_test(cx, |_| {});
 5080
 5081    let mut cx = EditorTestContext::new(cx).await;
 5082
 5083    let rust_language = Arc::new(
 5084        Language::new(
 5085            LanguageConfig {
 5086                name: "Rust".into(),
 5087                brackets: serde_json::from_value(json!([
 5088                    { "start": "{", "end": "}", "close": true, "newline": true },
 5089                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 5090                ]))
 5091                .unwrap(),
 5092                autoclose_before: "})]>".into(),
 5093                ..Default::default()
 5094            },
 5095            Some(tree_sitter_rust::language()),
 5096        )
 5097        .with_override_query("(string_literal) @string")
 5098        .unwrap(),
 5099    );
 5100
 5101    cx.language_registry().add(rust_language.clone());
 5102    cx.update_buffer(|buffer, cx| {
 5103        buffer.set_language(Some(rust_language), cx);
 5104    });
 5105
 5106    cx.set_state(
 5107        &r#"
 5108            let x = ˇ
 5109        "#
 5110        .unindent(),
 5111    );
 5112
 5113    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 5114    cx.update_editor(|editor, cx| {
 5115        editor.handle_input("\"", cx);
 5116    });
 5117    cx.assert_editor_state(
 5118        &r#"
 5119            let x = "ˇ"
 5120        "#
 5121        .unindent(),
 5122    );
 5123
 5124    // Inserting another quotation mark. The cursor moves across the existing
 5125    // automatically-inserted quotation mark.
 5126    cx.update_editor(|editor, cx| {
 5127        editor.handle_input("\"", cx);
 5128    });
 5129    cx.assert_editor_state(
 5130        &r#"
 5131            let x = ""ˇ
 5132        "#
 5133        .unindent(),
 5134    );
 5135
 5136    // Reset
 5137    cx.set_state(
 5138        &r#"
 5139            let x = ˇ
 5140        "#
 5141        .unindent(),
 5142    );
 5143
 5144    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 5145    cx.update_editor(|editor, cx| {
 5146        editor.handle_input("\"", cx);
 5147        editor.handle_input(" ", cx);
 5148        editor.move_left(&Default::default(), cx);
 5149        editor.handle_input("\\", cx);
 5150        editor.handle_input("\"", cx);
 5151    });
 5152    cx.assert_editor_state(
 5153        &r#"
 5154            let x = "\"ˇ "
 5155        "#
 5156        .unindent(),
 5157    );
 5158
 5159    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 5160    // mark. Nothing is inserted.
 5161    cx.update_editor(|editor, cx| {
 5162        editor.move_right(&Default::default(), cx);
 5163        editor.handle_input("\"", cx);
 5164    });
 5165    cx.assert_editor_state(
 5166        &r#"
 5167            let x = "\" "ˇ
 5168        "#
 5169        .unindent(),
 5170    );
 5171}
 5172
 5173#[gpui::test]
 5174async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
 5175    init_test(cx, |_| {});
 5176
 5177    let language = Arc::new(Language::new(
 5178        LanguageConfig {
 5179            brackets: BracketPairConfig {
 5180                pairs: vec![
 5181                    BracketPair {
 5182                        start: "{".to_string(),
 5183                        end: "}".to_string(),
 5184                        close: true,
 5185                        newline: true,
 5186                    },
 5187                    BracketPair {
 5188                        start: "/* ".to_string(),
 5189                        end: "*/".to_string(),
 5190                        close: true,
 5191                        ..Default::default()
 5192                    },
 5193                ],
 5194                ..Default::default()
 5195            },
 5196            ..Default::default()
 5197        },
 5198        Some(tree_sitter_rust::language()),
 5199    ));
 5200
 5201    let text = r#"
 5202        a
 5203        b
 5204        c
 5205    "#
 5206    .unindent();
 5207
 5208    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 5209    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5210    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5211    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5212        .await;
 5213
 5214    _ = view.update(cx, |view, cx| {
 5215        view.change_selections(None, cx, |s| {
 5216            s.select_display_ranges([
 5217                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 5218                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 5219                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
 5220            ])
 5221        });
 5222
 5223        view.handle_input("{", cx);
 5224        view.handle_input("{", cx);
 5225        view.handle_input("{", cx);
 5226        assert_eq!(
 5227            view.text(cx),
 5228            "
 5229                {{{a}}}
 5230                {{{b}}}
 5231                {{{c}}}
 5232            "
 5233            .unindent()
 5234        );
 5235        assert_eq!(
 5236            view.selections.display_ranges(cx),
 5237            [
 5238                DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
 5239                DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
 5240                DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
 5241            ]
 5242        );
 5243
 5244        view.undo(&Undo, cx);
 5245        view.undo(&Undo, cx);
 5246        view.undo(&Undo, cx);
 5247        assert_eq!(
 5248            view.text(cx),
 5249            "
 5250                a
 5251                b
 5252                c
 5253            "
 5254            .unindent()
 5255        );
 5256        assert_eq!(
 5257            view.selections.display_ranges(cx),
 5258            [
 5259                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 5260                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 5261                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
 5262            ]
 5263        );
 5264
 5265        // Ensure inserting the first character of a multi-byte bracket pair
 5266        // doesn't surround the selections with the bracket.
 5267        view.handle_input("/", cx);
 5268        assert_eq!(
 5269            view.text(cx),
 5270            "
 5271                /
 5272                /
 5273                /
 5274            "
 5275            .unindent()
 5276        );
 5277        assert_eq!(
 5278            view.selections.display_ranges(cx),
 5279            [
 5280                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 5281                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 5282                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
 5283            ]
 5284        );
 5285
 5286        view.undo(&Undo, cx);
 5287        assert_eq!(
 5288            view.text(cx),
 5289            "
 5290                a
 5291                b
 5292                c
 5293            "
 5294            .unindent()
 5295        );
 5296        assert_eq!(
 5297            view.selections.display_ranges(cx),
 5298            [
 5299                DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
 5300                DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
 5301                DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
 5302            ]
 5303        );
 5304
 5305        // Ensure inserting the last character of a multi-byte bracket pair
 5306        // doesn't surround the selections with the bracket.
 5307        view.handle_input("*", cx);
 5308        assert_eq!(
 5309            view.text(cx),
 5310            "
 5311                *
 5312                *
 5313                *
 5314            "
 5315            .unindent()
 5316        );
 5317        assert_eq!(
 5318            view.selections.display_ranges(cx),
 5319            [
 5320                DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
 5321                DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
 5322                DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
 5323            ]
 5324        );
 5325    });
 5326}
 5327
 5328#[gpui::test]
 5329async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
 5330    init_test(cx, |_| {});
 5331
 5332    let language = Arc::new(Language::new(
 5333        LanguageConfig {
 5334            brackets: BracketPairConfig {
 5335                pairs: vec![BracketPair {
 5336                    start: "{".to_string(),
 5337                    end: "}".to_string(),
 5338                    close: true,
 5339                    newline: true,
 5340                }],
 5341                ..Default::default()
 5342            },
 5343            autoclose_before: "}".to_string(),
 5344            ..Default::default()
 5345        },
 5346        Some(tree_sitter_rust::language()),
 5347    ));
 5348
 5349    let text = r#"
 5350        a
 5351        b
 5352        c
 5353    "#
 5354    .unindent();
 5355
 5356    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 5357    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5358    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5359    editor
 5360        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5361        .await;
 5362
 5363    _ = editor.update(cx, |editor, cx| {
 5364        editor.change_selections(None, cx, |s| {
 5365            s.select_ranges([
 5366                Point::new(0, 1)..Point::new(0, 1),
 5367                Point::new(1, 1)..Point::new(1, 1),
 5368                Point::new(2, 1)..Point::new(2, 1),
 5369            ])
 5370        });
 5371
 5372        editor.handle_input("{", cx);
 5373        editor.handle_input("{", cx);
 5374        editor.handle_input("_", cx);
 5375        assert_eq!(
 5376            editor.text(cx),
 5377            "
 5378                a{{_}}
 5379                b{{_}}
 5380                c{{_}}
 5381            "
 5382            .unindent()
 5383        );
 5384        assert_eq!(
 5385            editor.selections.ranges::<Point>(cx),
 5386            [
 5387                Point::new(0, 4)..Point::new(0, 4),
 5388                Point::new(1, 4)..Point::new(1, 4),
 5389                Point::new(2, 4)..Point::new(2, 4)
 5390            ]
 5391        );
 5392
 5393        editor.backspace(&Default::default(), cx);
 5394        editor.backspace(&Default::default(), cx);
 5395        assert_eq!(
 5396            editor.text(cx),
 5397            "
 5398                a{}
 5399                b{}
 5400                c{}
 5401            "
 5402            .unindent()
 5403        );
 5404        assert_eq!(
 5405            editor.selections.ranges::<Point>(cx),
 5406            [
 5407                Point::new(0, 2)..Point::new(0, 2),
 5408                Point::new(1, 2)..Point::new(1, 2),
 5409                Point::new(2, 2)..Point::new(2, 2)
 5410            ]
 5411        );
 5412
 5413        editor.delete_to_previous_word_start(&Default::default(), cx);
 5414        assert_eq!(
 5415            editor.text(cx),
 5416            "
 5417                a
 5418                b
 5419                c
 5420            "
 5421            .unindent()
 5422        );
 5423        assert_eq!(
 5424            editor.selections.ranges::<Point>(cx),
 5425            [
 5426                Point::new(0, 1)..Point::new(0, 1),
 5427                Point::new(1, 1)..Point::new(1, 1),
 5428                Point::new(2, 1)..Point::new(2, 1)
 5429            ]
 5430        );
 5431    });
 5432}
 5433
 5434#[gpui::test]
 5435async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
 5436    init_test(cx, |settings| {
 5437        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 5438    });
 5439
 5440    let mut cx = EditorTestContext::new(cx).await;
 5441
 5442    let language = Arc::new(Language::new(
 5443        LanguageConfig {
 5444            brackets: BracketPairConfig {
 5445                pairs: vec![
 5446                    BracketPair {
 5447                        start: "{".to_string(),
 5448                        end: "}".to_string(),
 5449                        close: true,
 5450                        newline: true,
 5451                    },
 5452                    BracketPair {
 5453                        start: "(".to_string(),
 5454                        end: ")".to_string(),
 5455                        close: true,
 5456                        newline: true,
 5457                    },
 5458                    BracketPair {
 5459                        start: "[".to_string(),
 5460                        end: "]".to_string(),
 5461                        close: false,
 5462                        newline: true,
 5463                    },
 5464                ],
 5465                ..Default::default()
 5466            },
 5467            autoclose_before: "})]".to_string(),
 5468            ..Default::default()
 5469        },
 5470        Some(tree_sitter_rust::language()),
 5471    ));
 5472
 5473    cx.language_registry().add(language.clone());
 5474    cx.update_buffer(|buffer, cx| {
 5475        buffer.set_language(Some(language), cx);
 5476    });
 5477
 5478    cx.set_state(
 5479        &"
 5480            {(ˇ)}
 5481            [[ˇ]]
 5482            {(ˇ)}
 5483        "
 5484        .unindent(),
 5485    );
 5486
 5487    cx.update_editor(|view, cx| {
 5488        view.backspace(&Default::default(), cx);
 5489        view.backspace(&Default::default(), cx);
 5490    });
 5491
 5492    cx.assert_editor_state(
 5493        &"
 5494            ˇ
 5495            ˇ]]
 5496            ˇ
 5497        "
 5498        .unindent(),
 5499    );
 5500
 5501    cx.update_editor(|view, cx| {
 5502        view.handle_input("{", cx);
 5503        view.handle_input("{", cx);
 5504        view.move_right(&MoveRight, cx);
 5505        view.move_right(&MoveRight, cx);
 5506        view.move_left(&MoveLeft, cx);
 5507        view.move_left(&MoveLeft, cx);
 5508        view.backspace(&Default::default(), cx);
 5509    });
 5510
 5511    cx.assert_editor_state(
 5512        &"
 5513            {ˇ}
 5514            {ˇ}]]
 5515            {ˇ}
 5516        "
 5517        .unindent(),
 5518    );
 5519
 5520    cx.update_editor(|view, cx| {
 5521        view.backspace(&Default::default(), cx);
 5522    });
 5523
 5524    cx.assert_editor_state(
 5525        &"
 5526            ˇ
 5527            ˇ]]
 5528            ˇ
 5529        "
 5530        .unindent(),
 5531    );
 5532}
 5533
 5534#[gpui::test]
 5535async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
 5536    init_test(cx, |_| {});
 5537
 5538    let language = Arc::new(Language::new(
 5539        LanguageConfig::default(),
 5540        Some(tree_sitter_rust::language()),
 5541    ));
 5542
 5543    let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
 5544    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5545    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5546    editor
 5547        .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 5548        .await;
 5549
 5550    _ = editor.update(cx, |editor, cx| {
 5551        editor.set_auto_replace_emoji_shortcode(true);
 5552
 5553        editor.handle_input("Hello ", cx);
 5554        editor.handle_input(":wave", cx);
 5555        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 5556
 5557        editor.handle_input(":", cx);
 5558        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 5559
 5560        editor.handle_input(" :smile", cx);
 5561        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 5562
 5563        editor.handle_input(":", cx);
 5564        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 5565
 5566        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 5567        editor.handle_input(":wave", cx);
 5568        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 5569
 5570        editor.handle_input(":", cx);
 5571        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 5572
 5573        editor.handle_input(":1", cx);
 5574        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 5575
 5576        editor.handle_input(":", cx);
 5577        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 5578
 5579        // Ensure shortcode does not get replaced when it is part of a word
 5580        editor.handle_input(" Test:wave", cx);
 5581        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 5582
 5583        editor.handle_input(":", cx);
 5584        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 5585
 5586        editor.set_auto_replace_emoji_shortcode(false);
 5587
 5588        // Ensure shortcode does not get replaced when auto replace is off
 5589        editor.handle_input(" :wave", cx);
 5590        assert_eq!(
 5591            editor.text(cx),
 5592            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 5593        );
 5594
 5595        editor.handle_input(":", cx);
 5596        assert_eq!(
 5597            editor.text(cx),
 5598            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 5599        );
 5600    });
 5601}
 5602
 5603#[gpui::test]
 5604async fn test_snippets(cx: &mut gpui::TestAppContext) {
 5605    init_test(cx, |_| {});
 5606
 5607    let (text, insertion_ranges) = marked_text_ranges(
 5608        indoc! {"
 5609            a.ˇ b
 5610            a.ˇ b
 5611            a.ˇ b
 5612        "},
 5613        false,
 5614    );
 5615
 5616    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 5617    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5618
 5619    _ = editor.update(cx, |editor, cx| {
 5620        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 5621
 5622        editor
 5623            .insert_snippet(&insertion_ranges, snippet, cx)
 5624            .unwrap();
 5625
 5626        fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
 5627            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 5628            assert_eq!(editor.text(cx), expected_text);
 5629            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 5630        }
 5631
 5632        assert(
 5633            editor,
 5634            cx,
 5635            indoc! {"
 5636                a.f(«one», two, «three») b
 5637                a.f(«one», two, «three») b
 5638                a.f(«one», two, «three») b
 5639            "},
 5640        );
 5641
 5642        // Can't move earlier than the first tab stop
 5643        assert!(!editor.move_to_prev_snippet_tabstop(cx));
 5644        assert(
 5645            editor,
 5646            cx,
 5647            indoc! {"
 5648                a.f(«one», two, «three») b
 5649                a.f(«one», two, «three») b
 5650                a.f(«one», two, «three») b
 5651            "},
 5652        );
 5653
 5654        assert!(editor.move_to_next_snippet_tabstop(cx));
 5655        assert(
 5656            editor,
 5657            cx,
 5658            indoc! {"
 5659                a.f(one, «two», three) b
 5660                a.f(one, «two», three) b
 5661                a.f(one, «two», three) b
 5662            "},
 5663        );
 5664
 5665        editor.move_to_prev_snippet_tabstop(cx);
 5666        assert(
 5667            editor,
 5668            cx,
 5669            indoc! {"
 5670                a.f(«one», two, «three») b
 5671                a.f(«one», two, «three») b
 5672                a.f(«one», two, «three») b
 5673            "},
 5674        );
 5675
 5676        assert!(editor.move_to_next_snippet_tabstop(cx));
 5677        assert(
 5678            editor,
 5679            cx,
 5680            indoc! {"
 5681                a.f(one, «two», three) b
 5682                a.f(one, «two», three) b
 5683                a.f(one, «two», three) b
 5684            "},
 5685        );
 5686        assert!(editor.move_to_next_snippet_tabstop(cx));
 5687        assert(
 5688            editor,
 5689            cx,
 5690            indoc! {"
 5691                a.f(one, two, three)ˇ b
 5692                a.f(one, two, three)ˇ b
 5693                a.f(one, two, three)ˇ b
 5694            "},
 5695        );
 5696
 5697        // As soon as the last tab stop is reached, snippet state is gone
 5698        editor.move_to_prev_snippet_tabstop(cx);
 5699        assert(
 5700            editor,
 5701            cx,
 5702            indoc! {"
 5703                a.f(one, two, three)ˇ b
 5704                a.f(one, two, three)ˇ b
 5705                a.f(one, two, three)ˇ b
 5706            "},
 5707        );
 5708    });
 5709}
 5710
 5711#[gpui::test]
 5712async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
 5713    init_test(cx, |_| {});
 5714
 5715    let fs = FakeFs::new(cx.executor());
 5716    fs.insert_file("/file.rs", Default::default()).await;
 5717
 5718    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 5719
 5720    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 5721    language_registry.add(rust_lang());
 5722    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 5723        "Rust",
 5724        FakeLspAdapter {
 5725            capabilities: lsp::ServerCapabilities {
 5726                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 5727                ..Default::default()
 5728            },
 5729            ..Default::default()
 5730        },
 5731    );
 5732
 5733    let buffer = project
 5734        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 5735        .await
 5736        .unwrap();
 5737
 5738    cx.executor().start_waiting();
 5739    let fake_server = fake_servers.next().await.unwrap();
 5740
 5741    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 5742    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 5743    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 5744    assert!(cx.read(|cx| editor.is_dirty(cx)));
 5745
 5746    let save = editor
 5747        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5748        .unwrap();
 5749    fake_server
 5750        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 5751            assert_eq!(
 5752                params.text_document.uri,
 5753                lsp::Url::from_file_path("/file.rs").unwrap()
 5754            );
 5755            assert_eq!(params.options.tab_size, 4);
 5756            Ok(Some(vec![lsp::TextEdit::new(
 5757                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 5758                ", ".to_string(),
 5759            )]))
 5760        })
 5761        .next()
 5762        .await;
 5763    cx.executor().start_waiting();
 5764    save.await;
 5765
 5766    assert_eq!(
 5767        editor.update(cx, |editor, cx| editor.text(cx)),
 5768        "one, two\nthree\n"
 5769    );
 5770    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 5771
 5772    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 5773    assert!(cx.read(|cx| editor.is_dirty(cx)));
 5774
 5775    // Ensure we can still save even if formatting hangs.
 5776    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 5777        assert_eq!(
 5778            params.text_document.uri,
 5779            lsp::Url::from_file_path("/file.rs").unwrap()
 5780        );
 5781        futures::future::pending::<()>().await;
 5782        unreachable!()
 5783    });
 5784    let save = editor
 5785        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5786        .unwrap();
 5787    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 5788    cx.executor().start_waiting();
 5789    save.await;
 5790    assert_eq!(
 5791        editor.update(cx, |editor, cx| editor.text(cx)),
 5792        "one\ntwo\nthree\n"
 5793    );
 5794    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 5795
 5796    // For non-dirty buffer, no formatting request should be sent
 5797    let save = editor
 5798        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5799        .unwrap();
 5800    let _pending_format_request = fake_server
 5801        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 5802            panic!("Should not be invoked on non-dirty buffer");
 5803        })
 5804        .next();
 5805    cx.executor().start_waiting();
 5806    save.await;
 5807
 5808    // Set rust language override and assert overridden tabsize is sent to language server
 5809    update_test_language_settings(cx, |settings| {
 5810        settings.languages.insert(
 5811            "Rust".into(),
 5812            LanguageSettingsContent {
 5813                tab_size: NonZeroU32::new(8),
 5814                ..Default::default()
 5815            },
 5816        );
 5817    });
 5818
 5819    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
 5820    assert!(cx.read(|cx| editor.is_dirty(cx)));
 5821    let save = editor
 5822        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 5823        .unwrap();
 5824    fake_server
 5825        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 5826            assert_eq!(
 5827                params.text_document.uri,
 5828                lsp::Url::from_file_path("/file.rs").unwrap()
 5829            );
 5830            assert_eq!(params.options.tab_size, 8);
 5831            Ok(Some(vec![]))
 5832        })
 5833        .next()
 5834        .await;
 5835    cx.executor().start_waiting();
 5836    save.await;
 5837}
 5838
 5839#[gpui::test]
 5840async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
 5841    init_test(cx, |_| {});
 5842
 5843    let cols = 4;
 5844    let rows = 10;
 5845    let sample_text_1 = sample_text(rows, cols, 'a');
 5846    assert_eq!(
 5847        sample_text_1,
 5848        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 5849    );
 5850    let sample_text_2 = sample_text(rows, cols, 'l');
 5851    assert_eq!(
 5852        sample_text_2,
 5853        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 5854    );
 5855    let sample_text_3 = sample_text(rows, cols, 'v');
 5856    assert_eq!(
 5857        sample_text_3,
 5858        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 5859    );
 5860
 5861    let fs = FakeFs::new(cx.executor());
 5862    fs.insert_tree(
 5863        "/a",
 5864        json!({
 5865            "main.rs": sample_text_1,
 5866            "other.rs": sample_text_2,
 5867            "lib.rs": sample_text_3,
 5868        }),
 5869    )
 5870    .await;
 5871
 5872    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 5873    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 5874    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 5875
 5876    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 5877    language_registry.add(rust_lang());
 5878    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 5879        "Rust",
 5880        FakeLspAdapter {
 5881            capabilities: lsp::ServerCapabilities {
 5882                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 5883                ..Default::default()
 5884            },
 5885            ..Default::default()
 5886        },
 5887    );
 5888
 5889    let worktree = project.update(cx, |project, _| {
 5890        let mut worktrees = project.worktrees().collect::<Vec<_>>();
 5891        assert_eq!(worktrees.len(), 1);
 5892        worktrees.pop().unwrap()
 5893    });
 5894    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 5895
 5896    let buffer_1 = project
 5897        .update(cx, |project, cx| {
 5898            project.open_buffer((worktree_id, "main.rs"), cx)
 5899        })
 5900        .await
 5901        .unwrap();
 5902    let buffer_2 = project
 5903        .update(cx, |project, cx| {
 5904            project.open_buffer((worktree_id, "other.rs"), cx)
 5905        })
 5906        .await
 5907        .unwrap();
 5908    let buffer_3 = project
 5909        .update(cx, |project, cx| {
 5910            project.open_buffer((worktree_id, "lib.rs"), cx)
 5911        })
 5912        .await
 5913        .unwrap();
 5914
 5915    let multi_buffer = cx.new_model(|cx| {
 5916        let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
 5917        multi_buffer.push_excerpts(
 5918            buffer_1.clone(),
 5919            [
 5920                ExcerptRange {
 5921                    context: Point::new(0, 0)..Point::new(3, 0),
 5922                    primary: None,
 5923                },
 5924                ExcerptRange {
 5925                    context: Point::new(5, 0)..Point::new(7, 0),
 5926                    primary: None,
 5927                },
 5928                ExcerptRange {
 5929                    context: Point::new(9, 0)..Point::new(10, 4),
 5930                    primary: None,
 5931                },
 5932            ],
 5933            cx,
 5934        );
 5935        multi_buffer.push_excerpts(
 5936            buffer_2.clone(),
 5937            [
 5938                ExcerptRange {
 5939                    context: Point::new(0, 0)..Point::new(3, 0),
 5940                    primary: None,
 5941                },
 5942                ExcerptRange {
 5943                    context: Point::new(5, 0)..Point::new(7, 0),
 5944                    primary: None,
 5945                },
 5946                ExcerptRange {
 5947                    context: Point::new(9, 0)..Point::new(10, 4),
 5948                    primary: None,
 5949                },
 5950            ],
 5951            cx,
 5952        );
 5953        multi_buffer.push_excerpts(
 5954            buffer_3.clone(),
 5955            [
 5956                ExcerptRange {
 5957                    context: Point::new(0, 0)..Point::new(3, 0),
 5958                    primary: None,
 5959                },
 5960                ExcerptRange {
 5961                    context: Point::new(5, 0)..Point::new(7, 0),
 5962                    primary: None,
 5963                },
 5964                ExcerptRange {
 5965                    context: Point::new(9, 0)..Point::new(10, 4),
 5966                    primary: None,
 5967                },
 5968            ],
 5969            cx,
 5970        );
 5971        multi_buffer
 5972    });
 5973    let multi_buffer_editor =
 5974        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
 5975
 5976    multi_buffer_editor.update(cx, |editor, cx| {
 5977        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 5978        editor.insert("|one|two|three|", cx);
 5979    });
 5980    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 5981    multi_buffer_editor.update(cx, |editor, cx| {
 5982        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 5983            s.select_ranges(Some(60..70))
 5984        });
 5985        editor.insert("|four|five|six|", cx);
 5986    });
 5987    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 5988
 5989    // First two buffers should be edited, but not the third one.
 5990    assert_eq!(
 5991        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 5992        "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}",
 5993    );
 5994    buffer_1.update(cx, |buffer, _| {
 5995        assert!(buffer.is_dirty());
 5996        assert_eq!(
 5997            buffer.text(),
 5998            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 5999        )
 6000    });
 6001    buffer_2.update(cx, |buffer, _| {
 6002        assert!(buffer.is_dirty());
 6003        assert_eq!(
 6004            buffer.text(),
 6005            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 6006        )
 6007    });
 6008    buffer_3.update(cx, |buffer, _| {
 6009        assert!(!buffer.is_dirty());
 6010        assert_eq!(buffer.text(), sample_text_3,)
 6011    });
 6012
 6013    cx.executor().start_waiting();
 6014    let save = multi_buffer_editor
 6015        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6016        .unwrap();
 6017
 6018    let fake_server = fake_servers.next().await.unwrap();
 6019    fake_server
 6020        .server
 6021        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6022            Ok(Some(vec![lsp::TextEdit::new(
 6023                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6024                format!("[{} formatted]", params.text_document.uri),
 6025            )]))
 6026        })
 6027        .detach();
 6028    save.await;
 6029
 6030    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 6031    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 6032    assert_eq!(
 6033        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 6034        "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}",
 6035    );
 6036    buffer_1.update(cx, |buffer, _| {
 6037        assert!(!buffer.is_dirty());
 6038        assert_eq!(
 6039            buffer.text(),
 6040            "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
 6041        )
 6042    });
 6043    buffer_2.update(cx, |buffer, _| {
 6044        assert!(!buffer.is_dirty());
 6045        assert_eq!(
 6046            buffer.text(),
 6047            "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
 6048        )
 6049    });
 6050    buffer_3.update(cx, |buffer, _| {
 6051        assert!(!buffer.is_dirty());
 6052        assert_eq!(buffer.text(), sample_text_3,)
 6053    });
 6054}
 6055
 6056#[gpui::test]
 6057async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
 6058    init_test(cx, |_| {});
 6059
 6060    let fs = FakeFs::new(cx.executor());
 6061    fs.insert_file("/file.rs", Default::default()).await;
 6062
 6063    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6064
 6065    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6066    language_registry.add(rust_lang());
 6067    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6068        "Rust",
 6069        FakeLspAdapter {
 6070            capabilities: lsp::ServerCapabilities {
 6071                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 6072                ..Default::default()
 6073            },
 6074            ..Default::default()
 6075        },
 6076    );
 6077
 6078    let buffer = project
 6079        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6080        .await
 6081        .unwrap();
 6082
 6083    cx.executor().start_waiting();
 6084    let fake_server = fake_servers.next().await.unwrap();
 6085
 6086    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6087    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6088    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6089    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6090
 6091    let save = editor
 6092        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6093        .unwrap();
 6094    fake_server
 6095        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 6096            assert_eq!(
 6097                params.text_document.uri,
 6098                lsp::Url::from_file_path("/file.rs").unwrap()
 6099            );
 6100            assert_eq!(params.options.tab_size, 4);
 6101            Ok(Some(vec![lsp::TextEdit::new(
 6102                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6103                ", ".to_string(),
 6104            )]))
 6105        })
 6106        .next()
 6107        .await;
 6108    cx.executor().start_waiting();
 6109    save.await;
 6110    assert_eq!(
 6111        editor.update(cx, |editor, cx| editor.text(cx)),
 6112        "one, two\nthree\n"
 6113    );
 6114    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6115
 6116    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6117    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6118
 6119    // Ensure we can still save even if formatting hangs.
 6120    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
 6121        move |params, _| async move {
 6122            assert_eq!(
 6123                params.text_document.uri,
 6124                lsp::Url::from_file_path("/file.rs").unwrap()
 6125            );
 6126            futures::future::pending::<()>().await;
 6127            unreachable!()
 6128        },
 6129    );
 6130    let save = editor
 6131        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6132        .unwrap();
 6133    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6134    cx.executor().start_waiting();
 6135    save.await;
 6136    assert_eq!(
 6137        editor.update(cx, |editor, cx| editor.text(cx)),
 6138        "one\ntwo\nthree\n"
 6139    );
 6140    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 6141
 6142    // For non-dirty buffer, no formatting request should be sent
 6143    let save = editor
 6144        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6145        .unwrap();
 6146    let _pending_format_request = fake_server
 6147        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 6148            panic!("Should not be invoked on non-dirty buffer");
 6149        })
 6150        .next();
 6151    cx.executor().start_waiting();
 6152    save.await;
 6153
 6154    // Set Rust language override and assert overridden tabsize is sent to language server
 6155    update_test_language_settings(cx, |settings| {
 6156        settings.languages.insert(
 6157            "Rust".into(),
 6158            LanguageSettingsContent {
 6159                tab_size: NonZeroU32::new(8),
 6160                ..Default::default()
 6161            },
 6162        );
 6163    });
 6164
 6165    editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
 6166    assert!(cx.read(|cx| editor.is_dirty(cx)));
 6167    let save = editor
 6168        .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
 6169        .unwrap();
 6170    fake_server
 6171        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 6172            assert_eq!(
 6173                params.text_document.uri,
 6174                lsp::Url::from_file_path("/file.rs").unwrap()
 6175            );
 6176            assert_eq!(params.options.tab_size, 8);
 6177            Ok(Some(vec![]))
 6178        })
 6179        .next()
 6180        .await;
 6181    cx.executor().start_waiting();
 6182    save.await;
 6183}
 6184
 6185#[gpui::test]
 6186async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
 6187    init_test(cx, |settings| {
 6188        settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
 6189    });
 6190
 6191    let fs = FakeFs::new(cx.executor());
 6192    fs.insert_file("/file.rs", Default::default()).await;
 6193
 6194    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 6195
 6196    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 6197    language_registry.add(Arc::new(Language::new(
 6198        LanguageConfig {
 6199            name: "Rust".into(),
 6200            matcher: LanguageMatcher {
 6201                path_suffixes: vec!["rs".to_string()],
 6202                ..Default::default()
 6203            },
 6204            // Enable Prettier formatting for the same buffer, and ensure
 6205            // LSP is called instead of Prettier.
 6206            prettier_parser_name: Some("test_parser".to_string()),
 6207            ..Default::default()
 6208        },
 6209        Some(tree_sitter_rust::language()),
 6210    )));
 6211    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 6212        "Rust",
 6213        FakeLspAdapter {
 6214            capabilities: lsp::ServerCapabilities {
 6215                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6216                ..Default::default()
 6217            },
 6218            ..Default::default()
 6219        },
 6220    );
 6221
 6222    let buffer = project
 6223        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 6224        .await
 6225        .unwrap();
 6226
 6227    cx.executor().start_waiting();
 6228    let fake_server = fake_servers.next().await.unwrap();
 6229
 6230    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 6231    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 6232    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6233
 6234    let format = editor
 6235        .update(cx, |editor, cx| {
 6236            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 6237        })
 6238        .unwrap();
 6239    fake_server
 6240        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6241            assert_eq!(
 6242                params.text_document.uri,
 6243                lsp::Url::from_file_path("/file.rs").unwrap()
 6244            );
 6245            assert_eq!(params.options.tab_size, 4);
 6246            Ok(Some(vec![lsp::TextEdit::new(
 6247                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 6248                ", ".to_string(),
 6249            )]))
 6250        })
 6251        .next()
 6252        .await;
 6253    cx.executor().start_waiting();
 6254    format.await;
 6255    assert_eq!(
 6256        editor.update(cx, |editor, cx| editor.text(cx)),
 6257        "one, two\nthree\n"
 6258    );
 6259
 6260    _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 6261    // Ensure we don't lock if formatting hangs.
 6262    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 6263        assert_eq!(
 6264            params.text_document.uri,
 6265            lsp::Url::from_file_path("/file.rs").unwrap()
 6266        );
 6267        futures::future::pending::<()>().await;
 6268        unreachable!()
 6269    });
 6270    let format = editor
 6271        .update(cx, |editor, cx| {
 6272            editor.perform_format(project, FormatTrigger::Manual, cx)
 6273        })
 6274        .unwrap();
 6275    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 6276    cx.executor().start_waiting();
 6277    format.await;
 6278    assert_eq!(
 6279        editor.update(cx, |editor, cx| editor.text(cx)),
 6280        "one\ntwo\nthree\n"
 6281    );
 6282}
 6283
 6284#[gpui::test]
 6285async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
 6286    init_test(cx, |_| {});
 6287
 6288    let mut cx = EditorLspTestContext::new_rust(
 6289        lsp::ServerCapabilities {
 6290            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6291            ..Default::default()
 6292        },
 6293        cx,
 6294    )
 6295    .await;
 6296
 6297    cx.set_state(indoc! {"
 6298        one.twoˇ
 6299    "});
 6300
 6301    // The format request takes a long time. When it completes, it inserts
 6302    // a newline and an indent before the `.`
 6303    cx.lsp
 6304        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
 6305            let executor = cx.background_executor().clone();
 6306            async move {
 6307                executor.timer(Duration::from_millis(100)).await;
 6308                Ok(Some(vec![lsp::TextEdit {
 6309                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 6310                    new_text: "\n    ".into(),
 6311                }]))
 6312            }
 6313        });
 6314
 6315    // Submit a format request.
 6316    let format_1 = cx
 6317        .update_editor(|editor, cx| editor.format(&Format, cx))
 6318        .unwrap();
 6319    cx.executor().run_until_parked();
 6320
 6321    // Submit a second format request.
 6322    let format_2 = cx
 6323        .update_editor(|editor, cx| editor.format(&Format, cx))
 6324        .unwrap();
 6325    cx.executor().run_until_parked();
 6326
 6327    // Wait for both format requests to complete
 6328    cx.executor().advance_clock(Duration::from_millis(200));
 6329    cx.executor().start_waiting();
 6330    format_1.await.unwrap();
 6331    cx.executor().start_waiting();
 6332    format_2.await.unwrap();
 6333
 6334    // The formatting edits only happens once.
 6335    cx.assert_editor_state(indoc! {"
 6336        one
 6337            .twoˇ
 6338    "});
 6339}
 6340
 6341#[gpui::test]
 6342async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
 6343    init_test(cx, |settings| {
 6344        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 6345    });
 6346
 6347    let mut cx = EditorLspTestContext::new_rust(
 6348        lsp::ServerCapabilities {
 6349            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 6350            ..Default::default()
 6351        },
 6352        cx,
 6353    )
 6354    .await;
 6355
 6356    // Set up a buffer white some trailing whitespace and no trailing newline.
 6357    cx.set_state(
 6358        &[
 6359            "one ",   //
 6360            "twoˇ",   //
 6361            "three ", //
 6362            "four",   //
 6363        ]
 6364        .join("\n"),
 6365    );
 6366
 6367    // Submit a format request.
 6368    let format = cx
 6369        .update_editor(|editor, cx| editor.format(&Format, cx))
 6370        .unwrap();
 6371
 6372    // Record which buffer changes have been sent to the language server
 6373    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 6374    cx.lsp
 6375        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 6376            let buffer_changes = buffer_changes.clone();
 6377            move |params, _| {
 6378                buffer_changes.lock().extend(
 6379                    params
 6380                        .content_changes
 6381                        .into_iter()
 6382                        .map(|e| (e.range.unwrap(), e.text)),
 6383                );
 6384            }
 6385        });
 6386
 6387    // Handle formatting requests to the language server.
 6388    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
 6389        let buffer_changes = buffer_changes.clone();
 6390        move |_, _| {
 6391            // When formatting is requested, trailing whitespace has already been stripped,
 6392            // and the trailing newline has already been added.
 6393            assert_eq!(
 6394                &buffer_changes.lock()[1..],
 6395                &[
 6396                    (
 6397                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 6398                        "".into()
 6399                    ),
 6400                    (
 6401                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 6402                        "".into()
 6403                    ),
 6404                    (
 6405                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 6406                        "\n".into()
 6407                    ),
 6408                ]
 6409            );
 6410
 6411            // Insert blank lines between each line of the buffer.
 6412            async move {
 6413                Ok(Some(vec![
 6414                    lsp::TextEdit {
 6415                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
 6416                        new_text: "\n".into(),
 6417                    },
 6418                    lsp::TextEdit {
 6419                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
 6420                        new_text: "\n".into(),
 6421                    },
 6422                ]))
 6423            }
 6424        }
 6425    });
 6426
 6427    // After formatting the buffer, the trailing whitespace is stripped,
 6428    // a newline is appended, and the edits provided by the language server
 6429    // have been applied.
 6430    format.await.unwrap();
 6431    cx.assert_editor_state(
 6432        &[
 6433            "one",   //
 6434            "",      //
 6435            "twoˇ",  //
 6436            "",      //
 6437            "three", //
 6438            "four",  //
 6439            "",      //
 6440        ]
 6441        .join("\n"),
 6442    );
 6443
 6444    // Undoing the formatting undoes the trailing whitespace removal, the
 6445    // trailing newline, and the LSP edits.
 6446    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 6447    cx.assert_editor_state(
 6448        &[
 6449            "one ",   //
 6450            "twoˇ",   //
 6451            "three ", //
 6452            "four",   //
 6453        ]
 6454        .join("\n"),
 6455    );
 6456}
 6457
 6458#[gpui::test]
 6459async fn test_completion(cx: &mut gpui::TestAppContext) {
 6460    init_test(cx, |_| {});
 6461
 6462    let mut cx = EditorLspTestContext::new_rust(
 6463        lsp::ServerCapabilities {
 6464            completion_provider: Some(lsp::CompletionOptions {
 6465                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 6466                resolve_provider: Some(true),
 6467                ..Default::default()
 6468            }),
 6469            ..Default::default()
 6470        },
 6471        cx,
 6472    )
 6473    .await;
 6474
 6475    cx.set_state(indoc! {"
 6476        oneˇ
 6477        two
 6478        three
 6479    "});
 6480    cx.simulate_keystroke(".");
 6481    handle_completion_request(
 6482        &mut cx,
 6483        indoc! {"
 6484            one.|<>
 6485            two
 6486            three
 6487        "},
 6488        vec!["first_completion", "second_completion"],
 6489    )
 6490    .await;
 6491    cx.condition(|editor, _| editor.context_menu_visible())
 6492        .await;
 6493    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6494        editor.context_menu_next(&Default::default(), cx);
 6495        editor
 6496            .confirm_completion(&ConfirmCompletion::default(), cx)
 6497            .unwrap()
 6498    });
 6499    cx.assert_editor_state(indoc! {"
 6500        one.second_completionˇ
 6501        two
 6502        three
 6503    "});
 6504
 6505    handle_resolve_completion_request(
 6506        &mut cx,
 6507        Some(vec![
 6508            (
 6509                //This overlaps with the primary completion edit which is
 6510                //misbehavior from the LSP spec, test that we filter it out
 6511                indoc! {"
 6512                    one.second_ˇcompletion
 6513                    two
 6514                    threeˇ
 6515                "},
 6516                "overlapping additional edit",
 6517            ),
 6518            (
 6519                indoc! {"
 6520                    one.second_completion
 6521                    two
 6522                    threeˇ
 6523                "},
 6524                "\nadditional edit",
 6525            ),
 6526        ]),
 6527    )
 6528    .await;
 6529    apply_additional_edits.await.unwrap();
 6530    cx.assert_editor_state(indoc! {"
 6531        one.second_completionˇ
 6532        two
 6533        three
 6534        additional edit
 6535    "});
 6536
 6537    cx.set_state(indoc! {"
 6538        one.second_completion
 6539        twoˇ
 6540        threeˇ
 6541        additional edit
 6542    "});
 6543    cx.simulate_keystroke(" ");
 6544    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6545    cx.simulate_keystroke("s");
 6546    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6547
 6548    cx.assert_editor_state(indoc! {"
 6549        one.second_completion
 6550        two sˇ
 6551        three sˇ
 6552        additional edit
 6553    "});
 6554    handle_completion_request(
 6555        &mut cx,
 6556        indoc! {"
 6557            one.second_completion
 6558            two s
 6559            three <s|>
 6560            additional edit
 6561        "},
 6562        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 6563    )
 6564    .await;
 6565    cx.condition(|editor, _| editor.context_menu_visible())
 6566        .await;
 6567
 6568    cx.simulate_keystroke("i");
 6569
 6570    handle_completion_request(
 6571        &mut cx,
 6572        indoc! {"
 6573            one.second_completion
 6574            two si
 6575            three <si|>
 6576            additional edit
 6577        "},
 6578        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 6579    )
 6580    .await;
 6581    cx.condition(|editor, _| editor.context_menu_visible())
 6582        .await;
 6583
 6584    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6585        editor
 6586            .confirm_completion(&ConfirmCompletion::default(), cx)
 6587            .unwrap()
 6588    });
 6589    cx.assert_editor_state(indoc! {"
 6590        one.second_completion
 6591        two sixth_completionˇ
 6592        three sixth_completionˇ
 6593        additional edit
 6594    "});
 6595
 6596    handle_resolve_completion_request(&mut cx, None).await;
 6597    apply_additional_edits.await.unwrap();
 6598
 6599    _ = cx.update(|cx| {
 6600        cx.update_global::<SettingsStore, _>(|settings, cx| {
 6601            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 6602                settings.show_completions_on_input = Some(false);
 6603            });
 6604        })
 6605    });
 6606    cx.set_state("editorˇ");
 6607    cx.simulate_keystroke(".");
 6608    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6609    cx.simulate_keystroke("c");
 6610    cx.simulate_keystroke("l");
 6611    cx.simulate_keystroke("o");
 6612    cx.assert_editor_state("editor.cloˇ");
 6613    assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
 6614    cx.update_editor(|editor, cx| {
 6615        editor.show_completions(&ShowCompletions, cx);
 6616    });
 6617    handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
 6618    cx.condition(|editor, _| editor.context_menu_visible())
 6619        .await;
 6620    let apply_additional_edits = cx.update_editor(|editor, cx| {
 6621        editor
 6622            .confirm_completion(&ConfirmCompletion::default(), cx)
 6623            .unwrap()
 6624    });
 6625    cx.assert_editor_state("editor.closeˇ");
 6626    handle_resolve_completion_request(&mut cx, None).await;
 6627    apply_additional_edits.await.unwrap();
 6628}
 6629
 6630#[gpui::test]
 6631async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
 6632    init_test(cx, |_| {});
 6633    let mut cx = EditorTestContext::new(cx).await;
 6634    let language = Arc::new(Language::new(
 6635        LanguageConfig {
 6636            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
 6637            ..Default::default()
 6638        },
 6639        Some(tree_sitter_rust::language()),
 6640    ));
 6641    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6642
 6643    // If multiple selections intersect a line, the line is only toggled once.
 6644    cx.set_state(indoc! {"
 6645        fn a() {
 6646            «//b();
 6647            ˇ»// «c();
 6648            //ˇ»  d();
 6649        }
 6650    "});
 6651
 6652    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6653
 6654    cx.assert_editor_state(indoc! {"
 6655        fn a() {
 6656            «b();
 6657            c();
 6658            ˇ» d();
 6659        }
 6660    "});
 6661
 6662    // The comment prefix is inserted at the same column for every line in a
 6663    // selection.
 6664    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6665
 6666    cx.assert_editor_state(indoc! {"
 6667        fn a() {
 6668            // «b();
 6669            // c();
 6670            ˇ»//  d();
 6671        }
 6672    "});
 6673
 6674    // If a selection ends at the beginning of a line, that line is not toggled.
 6675    cx.set_selections_state(indoc! {"
 6676        fn a() {
 6677            // b();
 6678            «// c();
 6679        ˇ»    //  d();
 6680        }
 6681    "});
 6682
 6683    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6684
 6685    cx.assert_editor_state(indoc! {"
 6686        fn a() {
 6687            // b();
 6688            «c();
 6689        ˇ»    //  d();
 6690        }
 6691    "});
 6692
 6693    // If a selection span a single line and is empty, the line is toggled.
 6694    cx.set_state(indoc! {"
 6695        fn a() {
 6696            a();
 6697            b();
 6698        ˇ
 6699        }
 6700    "});
 6701
 6702    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6703
 6704    cx.assert_editor_state(indoc! {"
 6705        fn a() {
 6706            a();
 6707            b();
 6708        //•ˇ
 6709        }
 6710    "});
 6711
 6712    // If a selection span multiple lines, empty lines are not toggled.
 6713    cx.set_state(indoc! {"
 6714        fn a() {
 6715            «a();
 6716
 6717            c();ˇ»
 6718        }
 6719    "});
 6720
 6721    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6722
 6723    cx.assert_editor_state(indoc! {"
 6724        fn a() {
 6725            // «a();
 6726
 6727            // c();ˇ»
 6728        }
 6729    "});
 6730
 6731    // If a selection includes multiple comment prefixes, all lines are uncommented.
 6732    cx.set_state(indoc! {"
 6733        fn a() {
 6734            «// a();
 6735            /// b();
 6736            //! c();ˇ»
 6737        }
 6738    "});
 6739
 6740    cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
 6741
 6742    cx.assert_editor_state(indoc! {"
 6743        fn a() {
 6744            «a();
 6745            b();
 6746            c();ˇ»
 6747        }
 6748    "});
 6749}
 6750
 6751#[gpui::test]
 6752async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
 6753    init_test(cx, |_| {});
 6754
 6755    let language = Arc::new(Language::new(
 6756        LanguageConfig {
 6757            line_comments: vec!["// ".into()],
 6758            ..Default::default()
 6759        },
 6760        Some(tree_sitter_rust::language()),
 6761    ));
 6762
 6763    let mut cx = EditorTestContext::new(cx).await;
 6764
 6765    cx.language_registry().add(language.clone());
 6766    cx.update_buffer(|buffer, cx| {
 6767        buffer.set_language(Some(language), cx);
 6768    });
 6769
 6770    let toggle_comments = &ToggleComments {
 6771        advance_downwards: true,
 6772    };
 6773
 6774    // Single cursor on one line -> advance
 6775    // Cursor moves horizontally 3 characters as well on non-blank line
 6776    cx.set_state(indoc!(
 6777        "fn a() {
 6778             ˇdog();
 6779             cat();
 6780        }"
 6781    ));
 6782    cx.update_editor(|editor, cx| {
 6783        editor.toggle_comments(toggle_comments, cx);
 6784    });
 6785    cx.assert_editor_state(indoc!(
 6786        "fn a() {
 6787             // dog();
 6788             catˇ();
 6789        }"
 6790    ));
 6791
 6792    // Single selection on one line -> don't advance
 6793    cx.set_state(indoc!(
 6794        "fn a() {
 6795             «dog()ˇ»;
 6796             cat();
 6797        }"
 6798    ));
 6799    cx.update_editor(|editor, cx| {
 6800        editor.toggle_comments(toggle_comments, cx);
 6801    });
 6802    cx.assert_editor_state(indoc!(
 6803        "fn a() {
 6804             // «dog()ˇ»;
 6805             cat();
 6806        }"
 6807    ));
 6808
 6809    // Multiple cursors on one line -> advance
 6810    cx.set_state(indoc!(
 6811        "fn a() {
 6812             ˇdˇog();
 6813             cat();
 6814        }"
 6815    ));
 6816    cx.update_editor(|editor, cx| {
 6817        editor.toggle_comments(toggle_comments, cx);
 6818    });
 6819    cx.assert_editor_state(indoc!(
 6820        "fn a() {
 6821             // dog();
 6822             catˇ(ˇ);
 6823        }"
 6824    ));
 6825
 6826    // Multiple cursors on one line, with selection -> don't advance
 6827    cx.set_state(indoc!(
 6828        "fn a() {
 6829             ˇdˇog«()ˇ»;
 6830             cat();
 6831        }"
 6832    ));
 6833    cx.update_editor(|editor, cx| {
 6834        editor.toggle_comments(toggle_comments, cx);
 6835    });
 6836    cx.assert_editor_state(indoc!(
 6837        "fn a() {
 6838             // ˇdˇog«()ˇ»;
 6839             cat();
 6840        }"
 6841    ));
 6842
 6843    // Single cursor on one line -> advance
 6844    // Cursor moves to column 0 on blank line
 6845    cx.set_state(indoc!(
 6846        "fn a() {
 6847             ˇdog();
 6848
 6849             cat();
 6850        }"
 6851    ));
 6852    cx.update_editor(|editor, cx| {
 6853        editor.toggle_comments(toggle_comments, cx);
 6854    });
 6855    cx.assert_editor_state(indoc!(
 6856        "fn a() {
 6857             // dog();
 6858        ˇ
 6859             cat();
 6860        }"
 6861    ));
 6862
 6863    // Single cursor on one line -> advance
 6864    // Cursor starts and ends at column 0
 6865    cx.set_state(indoc!(
 6866        "fn a() {
 6867         ˇ    dog();
 6868             cat();
 6869        }"
 6870    ));
 6871    cx.update_editor(|editor, cx| {
 6872        editor.toggle_comments(toggle_comments, cx);
 6873    });
 6874    cx.assert_editor_state(indoc!(
 6875        "fn a() {
 6876             // dog();
 6877         ˇ    cat();
 6878        }"
 6879    ));
 6880}
 6881
 6882#[gpui::test]
 6883async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 6884    init_test(cx, |_| {});
 6885
 6886    let mut cx = EditorTestContext::new(cx).await;
 6887
 6888    let html_language = Arc::new(
 6889        Language::new(
 6890            LanguageConfig {
 6891                name: "HTML".into(),
 6892                block_comment: Some(("<!-- ".into(), " -->".into())),
 6893                ..Default::default()
 6894            },
 6895            Some(tree_sitter_html::language()),
 6896        )
 6897        .with_injection_query(
 6898            r#"
 6899            (script_element
 6900                (raw_text) @content
 6901                (#set! "language" "javascript"))
 6902            "#,
 6903        )
 6904        .unwrap(),
 6905    );
 6906
 6907    let javascript_language = Arc::new(Language::new(
 6908        LanguageConfig {
 6909            name: "JavaScript".into(),
 6910            line_comments: vec!["// ".into()],
 6911            ..Default::default()
 6912        },
 6913        Some(tree_sitter_typescript::language_tsx()),
 6914    ));
 6915
 6916    cx.language_registry().add(html_language.clone());
 6917    cx.language_registry().add(javascript_language.clone());
 6918    cx.update_buffer(|buffer, cx| {
 6919        buffer.set_language(Some(html_language), cx);
 6920    });
 6921
 6922    // Toggle comments for empty selections
 6923    cx.set_state(
 6924        &r#"
 6925            <p>A</p>ˇ
 6926            <p>B</p>ˇ
 6927            <p>C</p>ˇ
 6928        "#
 6929        .unindent(),
 6930    );
 6931    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6932    cx.assert_editor_state(
 6933        &r#"
 6934            <!-- <p>A</p>ˇ -->
 6935            <!-- <p>B</p>ˇ -->
 6936            <!-- <p>C</p>ˇ -->
 6937        "#
 6938        .unindent(),
 6939    );
 6940    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6941    cx.assert_editor_state(
 6942        &r#"
 6943            <p>A</p>ˇ
 6944            <p>B</p>ˇ
 6945            <p>C</p>ˇ
 6946        "#
 6947        .unindent(),
 6948    );
 6949
 6950    // Toggle comments for mixture of empty and non-empty selections, where
 6951    // multiple selections occupy a given line.
 6952    cx.set_state(
 6953        &r#"
 6954            <p>A«</p>
 6955            <p>ˇ»B</p>ˇ
 6956            <p>C«</p>
 6957            <p>ˇ»D</p>ˇ
 6958        "#
 6959        .unindent(),
 6960    );
 6961
 6962    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6963    cx.assert_editor_state(
 6964        &r#"
 6965            <!-- <p>A«</p>
 6966            <p>ˇ»B</p>ˇ -->
 6967            <!-- <p>C«</p>
 6968            <p>ˇ»D</p>ˇ -->
 6969        "#
 6970        .unindent(),
 6971    );
 6972    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6973    cx.assert_editor_state(
 6974        &r#"
 6975            <p>A«</p>
 6976            <p>ˇ»B</p>ˇ
 6977            <p>C«</p>
 6978            <p>ˇ»D</p>ˇ
 6979        "#
 6980        .unindent(),
 6981    );
 6982
 6983    // Toggle comments when different languages are active for different
 6984    // selections.
 6985    cx.set_state(
 6986        &r#"
 6987            ˇ<script>
 6988                ˇvar x = new Y();
 6989            ˇ</script>
 6990        "#
 6991        .unindent(),
 6992    );
 6993    cx.executor().run_until_parked();
 6994    cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
 6995    cx.assert_editor_state(
 6996        &r#"
 6997            <!-- ˇ<script> -->
 6998                // ˇvar x = new Y();
 6999            <!-- ˇ</script> -->
 7000        "#
 7001        .unindent(),
 7002    );
 7003}
 7004
 7005#[gpui::test]
 7006fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
 7007    init_test(cx, |_| {});
 7008
 7009    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7010    let multibuffer = cx.new_model(|cx| {
 7011        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7012        multibuffer.push_excerpts(
 7013            buffer.clone(),
 7014            [
 7015                ExcerptRange {
 7016                    context: Point::new(0, 0)..Point::new(0, 4),
 7017                    primary: None,
 7018                },
 7019                ExcerptRange {
 7020                    context: Point::new(1, 0)..Point::new(1, 4),
 7021                    primary: None,
 7022                },
 7023            ],
 7024            cx,
 7025        );
 7026        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
 7027        multibuffer
 7028    });
 7029
 7030    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7031    _ = view.update(cx, |view, cx| {
 7032        assert_eq!(view.text(cx), "aaaa\nbbbb");
 7033        view.change_selections(None, cx, |s| {
 7034            s.select_ranges([
 7035                Point::new(0, 0)..Point::new(0, 0),
 7036                Point::new(1, 0)..Point::new(1, 0),
 7037            ])
 7038        });
 7039
 7040        view.handle_input("X", cx);
 7041        assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
 7042        assert_eq!(
 7043            view.selections.ranges(cx),
 7044            [
 7045                Point::new(0, 1)..Point::new(0, 1),
 7046                Point::new(1, 1)..Point::new(1, 1),
 7047            ]
 7048        );
 7049
 7050        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
 7051        view.change_selections(None, cx, |s| {
 7052            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
 7053        });
 7054        view.backspace(&Default::default(), cx);
 7055        assert_eq!(view.text(cx), "Xa\nbbb");
 7056        assert_eq!(
 7057            view.selections.ranges(cx),
 7058            [Point::new(1, 0)..Point::new(1, 0)]
 7059        );
 7060
 7061        view.change_selections(None, cx, |s| {
 7062            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
 7063        });
 7064        view.backspace(&Default::default(), cx);
 7065        assert_eq!(view.text(cx), "X\nbb");
 7066        assert_eq!(
 7067            view.selections.ranges(cx),
 7068            [Point::new(0, 1)..Point::new(0, 1)]
 7069        );
 7070    });
 7071}
 7072
 7073#[gpui::test]
 7074fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
 7075    init_test(cx, |_| {});
 7076
 7077    let markers = vec![('[', ']').into(), ('(', ')').into()];
 7078    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
 7079        indoc! {"
 7080            [aaaa
 7081            (bbbb]
 7082            cccc)",
 7083        },
 7084        markers.clone(),
 7085    );
 7086    let excerpt_ranges = markers.into_iter().map(|marker| {
 7087        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
 7088        ExcerptRange {
 7089            context,
 7090            primary: None,
 7091        }
 7092    });
 7093    let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
 7094    let multibuffer = cx.new_model(|cx| {
 7095        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7096        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
 7097        multibuffer
 7098    });
 7099
 7100    let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 7101    _ = view.update(cx, |view, cx| {
 7102        let (expected_text, selection_ranges) = marked_text_ranges(
 7103            indoc! {"
 7104                aaaa
 7105                bˇbbb
 7106                bˇbbˇb
 7107                cccc"
 7108            },
 7109            true,
 7110        );
 7111        assert_eq!(view.text(cx), expected_text);
 7112        view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
 7113
 7114        view.handle_input("X", cx);
 7115
 7116        let (expected_text, expected_selections) = marked_text_ranges(
 7117            indoc! {"
 7118                aaaa
 7119                bXˇbbXb
 7120                bXˇbbXˇb
 7121                cccc"
 7122            },
 7123            false,
 7124        );
 7125        assert_eq!(view.text(cx), expected_text);
 7126        assert_eq!(view.selections.ranges(cx), expected_selections);
 7127
 7128        view.newline(&Newline, cx);
 7129        let (expected_text, expected_selections) = marked_text_ranges(
 7130            indoc! {"
 7131                aaaa
 7132                bX
 7133                ˇbbX
 7134                b
 7135                bX
 7136                ˇbbX
 7137                ˇb
 7138                cccc"
 7139            },
 7140            false,
 7141        );
 7142        assert_eq!(view.text(cx), expected_text);
 7143        assert_eq!(view.selections.ranges(cx), expected_selections);
 7144    });
 7145}
 7146
 7147#[gpui::test]
 7148fn test_refresh_selections(cx: &mut TestAppContext) {
 7149    init_test(cx, |_| {});
 7150
 7151    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7152    let mut excerpt1_id = None;
 7153    let multibuffer = cx.new_model(|cx| {
 7154        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7155        excerpt1_id = multibuffer
 7156            .push_excerpts(
 7157                buffer.clone(),
 7158                [
 7159                    ExcerptRange {
 7160                        context: Point::new(0, 0)..Point::new(1, 4),
 7161                        primary: None,
 7162                    },
 7163                    ExcerptRange {
 7164                        context: Point::new(1, 0)..Point::new(2, 4),
 7165                        primary: None,
 7166                    },
 7167                ],
 7168                cx,
 7169            )
 7170            .into_iter()
 7171            .next();
 7172        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7173        multibuffer
 7174    });
 7175
 7176    let editor = cx.add_window(|cx| {
 7177        let mut editor = build_editor(multibuffer.clone(), cx);
 7178        let snapshot = editor.snapshot(cx);
 7179        editor.change_selections(None, cx, |s| {
 7180            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
 7181        });
 7182        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
 7183        assert_eq!(
 7184            editor.selections.ranges(cx),
 7185            [
 7186                Point::new(1, 3)..Point::new(1, 3),
 7187                Point::new(2, 1)..Point::new(2, 1),
 7188            ]
 7189        );
 7190        editor
 7191    });
 7192
 7193    // Refreshing selections is a no-op when excerpts haven't changed.
 7194    _ = editor.update(cx, |editor, cx| {
 7195        editor.change_selections(None, cx, |s| s.refresh());
 7196        assert_eq!(
 7197            editor.selections.ranges(cx),
 7198            [
 7199                Point::new(1, 3)..Point::new(1, 3),
 7200                Point::new(2, 1)..Point::new(2, 1),
 7201            ]
 7202        );
 7203    });
 7204
 7205    _ = multibuffer.update(cx, |multibuffer, cx| {
 7206        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7207    });
 7208    _ = editor.update(cx, |editor, cx| {
 7209        // Removing an excerpt causes the first selection to become degenerate.
 7210        assert_eq!(
 7211            editor.selections.ranges(cx),
 7212            [
 7213                Point::new(0, 0)..Point::new(0, 0),
 7214                Point::new(0, 1)..Point::new(0, 1)
 7215            ]
 7216        );
 7217
 7218        // Refreshing selections will relocate the first selection to the original buffer
 7219        // location.
 7220        editor.change_selections(None, cx, |s| s.refresh());
 7221        assert_eq!(
 7222            editor.selections.ranges(cx),
 7223            [
 7224                Point::new(0, 1)..Point::new(0, 1),
 7225                Point::new(0, 3)..Point::new(0, 3)
 7226            ]
 7227        );
 7228        assert!(editor.selections.pending_anchor().is_some());
 7229    });
 7230}
 7231
 7232#[gpui::test]
 7233fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
 7234    init_test(cx, |_| {});
 7235
 7236    let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
 7237    let mut excerpt1_id = None;
 7238    let multibuffer = cx.new_model(|cx| {
 7239        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 7240        excerpt1_id = multibuffer
 7241            .push_excerpts(
 7242                buffer.clone(),
 7243                [
 7244                    ExcerptRange {
 7245                        context: Point::new(0, 0)..Point::new(1, 4),
 7246                        primary: None,
 7247                    },
 7248                    ExcerptRange {
 7249                        context: Point::new(1, 0)..Point::new(2, 4),
 7250                        primary: None,
 7251                    },
 7252                ],
 7253                cx,
 7254            )
 7255            .into_iter()
 7256            .next();
 7257        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
 7258        multibuffer
 7259    });
 7260
 7261    let editor = cx.add_window(|cx| {
 7262        let mut editor = build_editor(multibuffer.clone(), cx);
 7263        let snapshot = editor.snapshot(cx);
 7264        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
 7265        assert_eq!(
 7266            editor.selections.ranges(cx),
 7267            [Point::new(1, 3)..Point::new(1, 3)]
 7268        );
 7269        editor
 7270    });
 7271
 7272    _ = multibuffer.update(cx, |multibuffer, cx| {
 7273        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
 7274    });
 7275    _ = editor.update(cx, |editor, cx| {
 7276        assert_eq!(
 7277            editor.selections.ranges(cx),
 7278            [Point::new(0, 0)..Point::new(0, 0)]
 7279        );
 7280
 7281        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
 7282        editor.change_selections(None, cx, |s| s.refresh());
 7283        assert_eq!(
 7284            editor.selections.ranges(cx),
 7285            [Point::new(0, 3)..Point::new(0, 3)]
 7286        );
 7287        assert!(editor.selections.pending_anchor().is_some());
 7288    });
 7289}
 7290
 7291#[gpui::test]
 7292async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 7293    init_test(cx, |_| {});
 7294
 7295    let language = Arc::new(
 7296        Language::new(
 7297            LanguageConfig {
 7298                brackets: BracketPairConfig {
 7299                    pairs: vec![
 7300                        BracketPair {
 7301                            start: "{".to_string(),
 7302                            end: "}".to_string(),
 7303                            close: true,
 7304                            newline: true,
 7305                        },
 7306                        BracketPair {
 7307                            start: "/* ".to_string(),
 7308                            end: " */".to_string(),
 7309                            close: true,
 7310                            newline: true,
 7311                        },
 7312                    ],
 7313                    ..Default::default()
 7314                },
 7315                ..Default::default()
 7316            },
 7317            Some(tree_sitter_rust::language()),
 7318        )
 7319        .with_indents_query("")
 7320        .unwrap(),
 7321    );
 7322
 7323    let text = concat!(
 7324        "{   }\n",     //
 7325        "  x\n",       //
 7326        "  /*   */\n", //
 7327        "x\n",         //
 7328        "{{} }\n",     //
 7329    );
 7330
 7331    let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
 7332    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 7333    let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 7334    view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
 7335        .await;
 7336
 7337    _ = view.update(cx, |view, cx| {
 7338        view.change_selections(None, cx, |s| {
 7339            s.select_display_ranges([
 7340                DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
 7341                DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
 7342                DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
 7343            ])
 7344        });
 7345        view.newline(&Newline, cx);
 7346
 7347        assert_eq!(
 7348            view.buffer().read(cx).read(cx).text(),
 7349            concat!(
 7350                "{ \n",    // Suppress rustfmt
 7351                "\n",      //
 7352                "}\n",     //
 7353                "  x\n",   //
 7354                "  /* \n", //
 7355                "  \n",    //
 7356                "  */\n",  //
 7357                "x\n",     //
 7358                "{{} \n",  //
 7359                "}\n",     //
 7360            )
 7361        );
 7362    });
 7363}
 7364
 7365#[gpui::test]
 7366fn test_highlighted_ranges(cx: &mut TestAppContext) {
 7367    init_test(cx, |_| {});
 7368
 7369    let editor = cx.add_window(|cx| {
 7370        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
 7371        build_editor(buffer.clone(), cx)
 7372    });
 7373
 7374    _ = editor.update(cx, |editor, cx| {
 7375        struct Type1;
 7376        struct Type2;
 7377
 7378        let buffer = editor.buffer.read(cx).snapshot(cx);
 7379
 7380        let anchor_range =
 7381            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
 7382
 7383        editor.highlight_background::<Type1>(
 7384            &[
 7385                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
 7386                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
 7387                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
 7388                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
 7389            ],
 7390            |_| Hsla::red(),
 7391            cx,
 7392        );
 7393        editor.highlight_background::<Type2>(
 7394            &[
 7395                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
 7396                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
 7397                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
 7398                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
 7399            ],
 7400            |_| Hsla::green(),
 7401            cx,
 7402        );
 7403
 7404        let snapshot = editor.snapshot(cx);
 7405        let mut highlighted_ranges = editor.background_highlights_in_range(
 7406            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
 7407            &snapshot,
 7408            cx.theme().colors(),
 7409        );
 7410        // Enforce a consistent ordering based on color without relying on the ordering of the
 7411        // highlight's `TypeId` which is non-executor.
 7412        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
 7413        assert_eq!(
 7414            highlighted_ranges,
 7415            &[
 7416                (
 7417                    DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
 7418                    Hsla::red(),
 7419                ),
 7420                (
 7421                    DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
 7422                    Hsla::red(),
 7423                ),
 7424                (
 7425                    DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
 7426                    Hsla::green(),
 7427                ),
 7428                (
 7429                    DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
 7430                    Hsla::green(),
 7431                ),
 7432            ]
 7433        );
 7434        assert_eq!(
 7435            editor.background_highlights_in_range(
 7436                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
 7437                &snapshot,
 7438                cx.theme().colors(),
 7439            ),
 7440            &[(
 7441                DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
 7442                Hsla::red(),
 7443            )]
 7444        );
 7445    });
 7446}
 7447
 7448#[gpui::test]
 7449async fn test_following(cx: &mut gpui::TestAppContext) {
 7450    init_test(cx, |_| {});
 7451
 7452    let fs = FakeFs::new(cx.executor());
 7453    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 7454
 7455    let buffer = project.update(cx, |project, cx| {
 7456        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
 7457        cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
 7458    });
 7459    let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 7460    let follower = cx.update(|cx| {
 7461        cx.open_window(
 7462            WindowOptions {
 7463                bounds: Some(Bounds::from_corners(
 7464                    gpui::Point::new(0.into(), 0.into()),
 7465                    gpui::Point::new(10.into(), 80.into()),
 7466                )),
 7467                ..Default::default()
 7468            },
 7469            |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
 7470        )
 7471    });
 7472
 7473    let is_still_following = Rc::new(RefCell::new(true));
 7474    let follower_edit_event_count = Rc::new(RefCell::new(0));
 7475    let pending_update = Rc::new(RefCell::new(None));
 7476    _ = follower.update(cx, {
 7477        let update = pending_update.clone();
 7478        let is_still_following = is_still_following.clone();
 7479        let follower_edit_event_count = follower_edit_event_count.clone();
 7480        |_, cx| {
 7481            cx.subscribe(
 7482                &leader.root_view(cx).unwrap(),
 7483                move |_, leader, event, cx| {
 7484                    leader
 7485                        .read(cx)
 7486                        .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 7487                },
 7488            )
 7489            .detach();
 7490
 7491            cx.subscribe(
 7492                &follower.root_view(cx).unwrap(),
 7493                move |_, _, event: &EditorEvent, _cx| {
 7494                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
 7495                        *is_still_following.borrow_mut() = false;
 7496                    }
 7497
 7498                    if let EditorEvent::BufferEdited = event {
 7499                        *follower_edit_event_count.borrow_mut() += 1;
 7500                    }
 7501                },
 7502            )
 7503            .detach();
 7504        }
 7505    });
 7506
 7507    // Update the selections only
 7508    _ = leader.update(cx, |leader, cx| {
 7509        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 7510    });
 7511    follower
 7512        .update(cx, |follower, cx| {
 7513            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7514        })
 7515        .unwrap()
 7516        .await
 7517        .unwrap();
 7518    _ = follower.update(cx, |follower, cx| {
 7519        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
 7520    });
 7521    assert_eq!(*is_still_following.borrow(), true);
 7522    assert_eq!(*follower_edit_event_count.borrow(), 0);
 7523
 7524    // Update the scroll position only
 7525    _ = leader.update(cx, |leader, cx| {
 7526        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 7527    });
 7528    follower
 7529        .update(cx, |follower, cx| {
 7530            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7531        })
 7532        .unwrap()
 7533        .await
 7534        .unwrap();
 7535    assert_eq!(
 7536        follower
 7537            .update(cx, |follower, cx| follower.scroll_position(cx))
 7538            .unwrap(),
 7539        gpui::Point::new(1.5, 3.5)
 7540    );
 7541    assert_eq!(*is_still_following.borrow(), true);
 7542    assert_eq!(*follower_edit_event_count.borrow(), 0);
 7543
 7544    // Update the selections and scroll position. The follower's scroll position is updated
 7545    // via autoscroll, not via the leader's exact scroll position.
 7546    _ = leader.update(cx, |leader, cx| {
 7547        leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
 7548        leader.request_autoscroll(Autoscroll::newest(), cx);
 7549        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
 7550    });
 7551    follower
 7552        .update(cx, |follower, cx| {
 7553            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7554        })
 7555        .unwrap()
 7556        .await
 7557        .unwrap();
 7558    _ = follower.update(cx, |follower, cx| {
 7559        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
 7560        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
 7561    });
 7562    assert_eq!(*is_still_following.borrow(), true);
 7563
 7564    // Creating a pending selection that precedes another selection
 7565    _ = leader.update(cx, |leader, cx| {
 7566        leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
 7567        leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
 7568    });
 7569    follower
 7570        .update(cx, |follower, cx| {
 7571            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7572        })
 7573        .unwrap()
 7574        .await
 7575        .unwrap();
 7576    _ = follower.update(cx, |follower, cx| {
 7577        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
 7578    });
 7579    assert_eq!(*is_still_following.borrow(), true);
 7580
 7581    // Extend the pending selection so that it surrounds another selection
 7582    _ = leader.update(cx, |leader, cx| {
 7583        leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
 7584    });
 7585    follower
 7586        .update(cx, |follower, cx| {
 7587            follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
 7588        })
 7589        .unwrap()
 7590        .await
 7591        .unwrap();
 7592    _ = follower.update(cx, |follower, cx| {
 7593        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
 7594    });
 7595
 7596    // Scrolling locally breaks the follow
 7597    _ = follower.update(cx, |follower, cx| {
 7598        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
 7599        follower.set_scroll_anchor(
 7600            ScrollAnchor {
 7601                anchor: top_anchor,
 7602                offset: gpui::Point::new(0.0, 0.5),
 7603            },
 7604            cx,
 7605        );
 7606    });
 7607    assert_eq!(*is_still_following.borrow(), false);
 7608}
 7609
 7610#[gpui::test]
 7611async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
 7612    init_test(cx, |_| {});
 7613
 7614    let fs = FakeFs::new(cx.executor());
 7615    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 7616    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 7617    let pane = workspace
 7618        .update(cx, |workspace, _| workspace.active_pane().clone())
 7619        .unwrap();
 7620
 7621    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 7622
 7623    let leader = pane.update(cx, |_, cx| {
 7624        let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
 7625        cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
 7626    });
 7627
 7628    // Start following the editor when it has no excerpts.
 7629    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 7630    let follower_1 = cx
 7631        .update_window(*workspace.deref(), |_, cx| {
 7632            Editor::from_state_proto(
 7633                pane.clone(),
 7634                workspace.root_view(cx).unwrap(),
 7635                ViewId {
 7636                    creator: Default::default(),
 7637                    id: 0,
 7638                },
 7639                &mut state_message,
 7640                cx,
 7641            )
 7642        })
 7643        .unwrap()
 7644        .unwrap()
 7645        .await
 7646        .unwrap();
 7647
 7648    let update_message = Rc::new(RefCell::new(None));
 7649    follower_1.update(cx, {
 7650        let update = update_message.clone();
 7651        |_, cx| {
 7652            cx.subscribe(&leader, move |_, leader, event, cx| {
 7653                leader
 7654                    .read(cx)
 7655                    .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
 7656            })
 7657            .detach();
 7658        }
 7659    });
 7660
 7661    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
 7662        (
 7663            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
 7664            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
 7665        )
 7666    });
 7667
 7668    // Insert some excerpts.
 7669    _ = leader.update(cx, |leader, cx| {
 7670        leader.buffer.update(cx, |multibuffer, cx| {
 7671            let excerpt_ids = multibuffer.push_excerpts(
 7672                buffer_1.clone(),
 7673                [
 7674                    ExcerptRange {
 7675                        context: 1..6,
 7676                        primary: None,
 7677                    },
 7678                    ExcerptRange {
 7679                        context: 12..15,
 7680                        primary: None,
 7681                    },
 7682                    ExcerptRange {
 7683                        context: 0..3,
 7684                        primary: None,
 7685                    },
 7686                ],
 7687                cx,
 7688            );
 7689            multibuffer.insert_excerpts_after(
 7690                excerpt_ids[0],
 7691                buffer_2.clone(),
 7692                [
 7693                    ExcerptRange {
 7694                        context: 8..12,
 7695                        primary: None,
 7696                    },
 7697                    ExcerptRange {
 7698                        context: 0..6,
 7699                        primary: None,
 7700                    },
 7701                ],
 7702                cx,
 7703            );
 7704        });
 7705    });
 7706
 7707    // Apply the update of adding the excerpts.
 7708    follower_1
 7709        .update(cx, |follower, cx| {
 7710            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 7711        })
 7712        .await
 7713        .unwrap();
 7714    assert_eq!(
 7715        follower_1.update(cx, |editor, cx| editor.text(cx)),
 7716        leader.update(cx, |editor, cx| editor.text(cx))
 7717    );
 7718    update_message.borrow_mut().take();
 7719
 7720    // Start following separately after it already has excerpts.
 7721    let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
 7722    let follower_2 = cx
 7723        .update_window(*workspace.deref(), |_, cx| {
 7724            Editor::from_state_proto(
 7725                pane.clone(),
 7726                workspace.root_view(cx).unwrap().clone(),
 7727                ViewId {
 7728                    creator: Default::default(),
 7729                    id: 0,
 7730                },
 7731                &mut state_message,
 7732                cx,
 7733            )
 7734        })
 7735        .unwrap()
 7736        .unwrap()
 7737        .await
 7738        .unwrap();
 7739    assert_eq!(
 7740        follower_2.update(cx, |editor, cx| editor.text(cx)),
 7741        leader.update(cx, |editor, cx| editor.text(cx))
 7742    );
 7743
 7744    // Remove some excerpts.
 7745    _ = leader.update(cx, |leader, cx| {
 7746        leader.buffer.update(cx, |multibuffer, cx| {
 7747            let excerpt_ids = multibuffer.excerpt_ids();
 7748            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
 7749            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
 7750        });
 7751    });
 7752
 7753    // Apply the update of removing the excerpts.
 7754    follower_1
 7755        .update(cx, |follower, cx| {
 7756            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 7757        })
 7758        .await
 7759        .unwrap();
 7760    follower_2
 7761        .update(cx, |follower, cx| {
 7762            follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
 7763        })
 7764        .await
 7765        .unwrap();
 7766    update_message.borrow_mut().take();
 7767    assert_eq!(
 7768        follower_1.update(cx, |editor, cx| editor.text(cx)),
 7769        leader.update(cx, |editor, cx| editor.text(cx))
 7770    );
 7771}
 7772
 7773#[gpui::test]
 7774async fn go_to_prev_overlapping_diagnostic(
 7775    executor: BackgroundExecutor,
 7776    cx: &mut gpui::TestAppContext,
 7777) {
 7778    init_test(cx, |_| {});
 7779
 7780    let mut cx = EditorTestContext::new(cx).await;
 7781    let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
 7782
 7783    cx.set_state(indoc! {"
 7784        ˇfn func(abc def: i32) -> u32 {
 7785        }
 7786    "});
 7787
 7788    _ = cx.update(|cx| {
 7789        _ = project.update(cx, |project, cx| {
 7790            project
 7791                .update_diagnostics(
 7792                    LanguageServerId(0),
 7793                    lsp::PublishDiagnosticsParams {
 7794                        uri: lsp::Url::from_file_path("/root/file").unwrap(),
 7795                        version: None,
 7796                        diagnostics: vec![
 7797                            lsp::Diagnostic {
 7798                                range: lsp::Range::new(
 7799                                    lsp::Position::new(0, 11),
 7800                                    lsp::Position::new(0, 12),
 7801                                ),
 7802                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 7803                                ..Default::default()
 7804                            },
 7805                            lsp::Diagnostic {
 7806                                range: lsp::Range::new(
 7807                                    lsp::Position::new(0, 12),
 7808                                    lsp::Position::new(0, 15),
 7809                                ),
 7810                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 7811                                ..Default::default()
 7812                            },
 7813                            lsp::Diagnostic {
 7814                                range: lsp::Range::new(
 7815                                    lsp::Position::new(0, 25),
 7816                                    lsp::Position::new(0, 28),
 7817                                ),
 7818                                severity: Some(lsp::DiagnosticSeverity::ERROR),
 7819                                ..Default::default()
 7820                            },
 7821                        ],
 7822                    },
 7823                    &[],
 7824                    cx,
 7825                )
 7826                .unwrap()
 7827        });
 7828    });
 7829
 7830    executor.run_until_parked();
 7831
 7832    cx.update_editor(|editor, cx| {
 7833        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7834    });
 7835
 7836    cx.assert_editor_state(indoc! {"
 7837        fn func(abc def: i32) -> ˇu32 {
 7838        }
 7839    "});
 7840
 7841    cx.update_editor(|editor, cx| {
 7842        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7843    });
 7844
 7845    cx.assert_editor_state(indoc! {"
 7846        fn func(abc ˇdef: i32) -> u32 {
 7847        }
 7848    "});
 7849
 7850    cx.update_editor(|editor, cx| {
 7851        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7852    });
 7853
 7854    cx.assert_editor_state(indoc! {"
 7855        fn func(abcˇ def: i32) -> u32 {
 7856        }
 7857    "});
 7858
 7859    cx.update_editor(|editor, cx| {
 7860        editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
 7861    });
 7862
 7863    cx.assert_editor_state(indoc! {"
 7864        fn func(abc def: i32) -> ˇu32 {
 7865        }
 7866    "});
 7867}
 7868
 7869#[gpui::test]
 7870async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 7871    init_test(cx, |_| {});
 7872
 7873    let mut cx = EditorTestContext::new(cx).await;
 7874
 7875    let diff_base = r#"
 7876        use some::mod;
 7877
 7878        const A: u32 = 42;
 7879
 7880        fn main() {
 7881            println!("hello");
 7882
 7883            println!("world");
 7884        }
 7885        "#
 7886    .unindent();
 7887
 7888    // Edits are modified, removed, modified, added
 7889    cx.set_state(
 7890        &r#"
 7891        use some::modified;
 7892
 7893        ˇ
 7894        fn main() {
 7895            println!("hello there");
 7896
 7897            println!("around the");
 7898            println!("world");
 7899        }
 7900        "#
 7901        .unindent(),
 7902    );
 7903
 7904    cx.set_diff_base(Some(&diff_base));
 7905    executor.run_until_parked();
 7906
 7907    cx.update_editor(|editor, cx| {
 7908        //Wrap around the bottom of the buffer
 7909        for _ in 0..3 {
 7910            editor.go_to_hunk(&GoToHunk, cx);
 7911        }
 7912    });
 7913
 7914    cx.assert_editor_state(
 7915        &r#"
 7916        ˇuse some::modified;
 7917
 7918
 7919        fn main() {
 7920            println!("hello there");
 7921
 7922            println!("around the");
 7923            println!("world");
 7924        }
 7925        "#
 7926        .unindent(),
 7927    );
 7928
 7929    cx.update_editor(|editor, cx| {
 7930        //Wrap around the top of the buffer
 7931        for _ in 0..2 {
 7932            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 7933        }
 7934    });
 7935
 7936    cx.assert_editor_state(
 7937        &r#"
 7938        use some::modified;
 7939
 7940
 7941        fn main() {
 7942        ˇ    println!("hello there");
 7943
 7944            println!("around the");
 7945            println!("world");
 7946        }
 7947        "#
 7948        .unindent(),
 7949    );
 7950
 7951    cx.update_editor(|editor, cx| {
 7952        editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 7953    });
 7954
 7955    cx.assert_editor_state(
 7956        &r#"
 7957        use some::modified;
 7958
 7959        ˇ
 7960        fn main() {
 7961            println!("hello there");
 7962
 7963            println!("around the");
 7964            println!("world");
 7965        }
 7966        "#
 7967        .unindent(),
 7968    );
 7969
 7970    cx.update_editor(|editor, cx| {
 7971        for _ in 0..3 {
 7972            editor.go_to_prev_hunk(&GoToPrevHunk, cx);
 7973        }
 7974    });
 7975
 7976    cx.assert_editor_state(
 7977        &r#"
 7978        use some::modified;
 7979
 7980
 7981        fn main() {
 7982        ˇ    println!("hello there");
 7983
 7984            println!("around the");
 7985            println!("world");
 7986        }
 7987        "#
 7988        .unindent(),
 7989    );
 7990
 7991    cx.update_editor(|editor, cx| {
 7992        editor.fold(&Fold, cx);
 7993
 7994        //Make sure that the fold only gets one hunk
 7995        for _ in 0..4 {
 7996            editor.go_to_hunk(&GoToHunk, cx);
 7997        }
 7998    });
 7999
 8000    cx.assert_editor_state(
 8001        &r#"
 8002        ˇuse some::modified;
 8003
 8004
 8005        fn main() {
 8006            println!("hello there");
 8007
 8008            println!("around the");
 8009            println!("world");
 8010        }
 8011        "#
 8012        .unindent(),
 8013    );
 8014}
 8015
 8016#[test]
 8017fn test_split_words() {
 8018    fn split(text: &str) -> Vec<&str> {
 8019        split_words(text).collect()
 8020    }
 8021
 8022    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
 8023    assert_eq!(split("hello_world"), &["hello_", "world"]);
 8024    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
 8025    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
 8026    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
 8027    assert_eq!(split("helloworld"), &["helloworld"]);
 8028
 8029    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
 8030}
 8031
 8032#[gpui::test]
 8033async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
 8034    init_test(cx, |_| {});
 8035
 8036    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
 8037    let mut assert = |before, after| {
 8038        let _state_context = cx.set_state(before);
 8039        cx.update_editor(|editor, cx| {
 8040            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
 8041        });
 8042        cx.assert_editor_state(after);
 8043    };
 8044
 8045    // Outside bracket jumps to outside of matching bracket
 8046    assert("console.logˇ(var);", "console.log(var)ˇ;");
 8047    assert("console.log(var)ˇ;", "console.logˇ(var);");
 8048
 8049    // Inside bracket jumps to inside of matching bracket
 8050    assert("console.log(ˇvar);", "console.log(varˇ);");
 8051    assert("console.log(varˇ);", "console.log(ˇvar);");
 8052
 8053    // When outside a bracket and inside, favor jumping to the inside bracket
 8054    assert(
 8055        "console.log('foo', [1, 2, 3]ˇ);",
 8056        "console.log(ˇ'foo', [1, 2, 3]);",
 8057    );
 8058    assert(
 8059        "console.log(ˇ'foo', [1, 2, 3]);",
 8060        "console.log('foo', [1, 2, 3]ˇ);",
 8061    );
 8062
 8063    // Bias forward if two options are equally likely
 8064    assert(
 8065        "let result = curried_fun()ˇ();",
 8066        "let result = curried_fun()()ˇ;",
 8067    );
 8068
 8069    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
 8070    assert(
 8071        indoc! {"
 8072            function test() {
 8073                console.log('test')ˇ
 8074            }"},
 8075        indoc! {"
 8076            function test() {
 8077                console.logˇ('test')
 8078            }"},
 8079    );
 8080}
 8081
 8082#[gpui::test]
 8083async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
 8084    init_test(cx, |_| {});
 8085
 8086    let fs = FakeFs::new(cx.executor());
 8087    fs.insert_tree(
 8088        "/a",
 8089        json!({
 8090            "main.rs": "fn main() { let a = 5; }",
 8091            "other.rs": "// Test file",
 8092        }),
 8093    )
 8094    .await;
 8095    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8096
 8097    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8098    language_registry.add(Arc::new(Language::new(
 8099        LanguageConfig {
 8100            name: "Rust".into(),
 8101            matcher: LanguageMatcher {
 8102                path_suffixes: vec!["rs".to_string()],
 8103                ..Default::default()
 8104            },
 8105            brackets: BracketPairConfig {
 8106                pairs: vec![BracketPair {
 8107                    start: "{".to_string(),
 8108                    end: "}".to_string(),
 8109                    close: true,
 8110                    newline: true,
 8111                }],
 8112                disabled_scopes_by_bracket_ix: Vec::new(),
 8113            },
 8114            ..Default::default()
 8115        },
 8116        Some(tree_sitter_rust::language()),
 8117    )));
 8118    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8119        "Rust",
 8120        FakeLspAdapter {
 8121            capabilities: lsp::ServerCapabilities {
 8122                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
 8123                    first_trigger_character: "{".to_string(),
 8124                    more_trigger_character: None,
 8125                }),
 8126                ..Default::default()
 8127            },
 8128            ..Default::default()
 8129        },
 8130    );
 8131
 8132    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8133
 8134    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 8135
 8136    let worktree_id = workspace
 8137        .update(cx, |workspace, cx| {
 8138            workspace.project().update(cx, |project, cx| {
 8139                project.worktrees().next().unwrap().read(cx).id()
 8140            })
 8141        })
 8142        .unwrap();
 8143
 8144    let buffer = project
 8145        .update(cx, |project, cx| {
 8146            project.open_local_buffer("/a/main.rs", cx)
 8147        })
 8148        .await
 8149        .unwrap();
 8150    cx.executor().run_until_parked();
 8151    cx.executor().start_waiting();
 8152    let fake_server = fake_servers.next().await.unwrap();
 8153    let editor_handle = workspace
 8154        .update(cx, |workspace, cx| {
 8155            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 8156        })
 8157        .unwrap()
 8158        .await
 8159        .unwrap()
 8160        .downcast::<Editor>()
 8161        .unwrap();
 8162
 8163    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
 8164        assert_eq!(
 8165            params.text_document_position.text_document.uri,
 8166            lsp::Url::from_file_path("/a/main.rs").unwrap(),
 8167        );
 8168        assert_eq!(
 8169            params.text_document_position.position,
 8170            lsp::Position::new(0, 21),
 8171        );
 8172
 8173        Ok(Some(vec![lsp::TextEdit {
 8174            new_text: "]".to_string(),
 8175            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
 8176        }]))
 8177    });
 8178
 8179    editor_handle.update(cx, |editor, cx| {
 8180        editor.focus(cx);
 8181        editor.change_selections(None, cx, |s| {
 8182            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
 8183        });
 8184        editor.handle_input("{", cx);
 8185    });
 8186
 8187    cx.executor().run_until_parked();
 8188
 8189    _ = buffer.update(cx, |buffer, _| {
 8190        assert_eq!(
 8191            buffer.text(),
 8192            "fn main() { let a = {5}; }",
 8193            "No extra braces from on type formatting should appear in the buffer"
 8194        )
 8195    });
 8196}
 8197
 8198#[gpui::test]
 8199async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
 8200    init_test(cx, |_| {});
 8201
 8202    let fs = FakeFs::new(cx.executor());
 8203    fs.insert_tree(
 8204        "/a",
 8205        json!({
 8206            "main.rs": "fn main() { let a = 5; }",
 8207            "other.rs": "// Test file",
 8208        }),
 8209    )
 8210    .await;
 8211
 8212    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 8213
 8214    let server_restarts = Arc::new(AtomicUsize::new(0));
 8215    let closure_restarts = Arc::clone(&server_restarts);
 8216    let language_server_name = "test language server";
 8217    let language_name: Arc<str> = "Rust".into();
 8218
 8219    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8220    language_registry.add(Arc::new(Language::new(
 8221        LanguageConfig {
 8222            name: Arc::clone(&language_name),
 8223            matcher: LanguageMatcher {
 8224                path_suffixes: vec!["rs".to_string()],
 8225                ..Default::default()
 8226            },
 8227            ..Default::default()
 8228        },
 8229        Some(tree_sitter_rust::language()),
 8230    )));
 8231    let mut fake_servers = language_registry.register_fake_lsp_adapter(
 8232        "Rust",
 8233        FakeLspAdapter {
 8234            name: language_server_name,
 8235            initialization_options: Some(json!({
 8236                "testOptionValue": true
 8237            })),
 8238            initializer: Some(Box::new(move |fake_server| {
 8239                let task_restarts = Arc::clone(&closure_restarts);
 8240                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
 8241                    task_restarts.fetch_add(1, atomic::Ordering::Release);
 8242                    futures::future::ready(Ok(()))
 8243                });
 8244            })),
 8245            ..Default::default()
 8246        },
 8247    );
 8248
 8249    let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 8250    let _buffer = project
 8251        .update(cx, |project, cx| {
 8252            project.open_local_buffer("/a/main.rs", cx)
 8253        })
 8254        .await
 8255        .unwrap();
 8256    let _fake_server = fake_servers.next().await.unwrap();
 8257    update_test_language_settings(cx, |language_settings| {
 8258        language_settings.languages.insert(
 8259            Arc::clone(&language_name),
 8260            LanguageSettingsContent {
 8261                tab_size: NonZeroU32::new(8),
 8262                ..Default::default()
 8263            },
 8264        );
 8265    });
 8266    cx.executor().run_until_parked();
 8267    assert_eq!(
 8268        server_restarts.load(atomic::Ordering::Acquire),
 8269        0,
 8270        "Should not restart LSP server on an unrelated change"
 8271    );
 8272
 8273    update_test_project_settings(cx, |project_settings| {
 8274        project_settings.lsp.insert(
 8275            "Some other server name".into(),
 8276            LspSettings {
 8277                binary: None,
 8278                settings: None,
 8279                initialization_options: Some(json!({
 8280                    "some other init value": false
 8281                })),
 8282            },
 8283        );
 8284    });
 8285    cx.executor().run_until_parked();
 8286    assert_eq!(
 8287        server_restarts.load(atomic::Ordering::Acquire),
 8288        0,
 8289        "Should not restart LSP server on an unrelated LSP settings change"
 8290    );
 8291
 8292    update_test_project_settings(cx, |project_settings| {
 8293        project_settings.lsp.insert(
 8294            language_server_name.into(),
 8295            LspSettings {
 8296                binary: None,
 8297                settings: None,
 8298                initialization_options: Some(json!({
 8299                    "anotherInitValue": false
 8300                })),
 8301            },
 8302        );
 8303    });
 8304    cx.executor().run_until_parked();
 8305    assert_eq!(
 8306        server_restarts.load(atomic::Ordering::Acquire),
 8307        1,
 8308        "Should restart LSP server on a related LSP settings change"
 8309    );
 8310
 8311    update_test_project_settings(cx, |project_settings| {
 8312        project_settings.lsp.insert(
 8313            language_server_name.into(),
 8314            LspSettings {
 8315                binary: None,
 8316                settings: None,
 8317                initialization_options: Some(json!({
 8318                    "anotherInitValue": false
 8319                })),
 8320            },
 8321        );
 8322    });
 8323    cx.executor().run_until_parked();
 8324    assert_eq!(
 8325        server_restarts.load(atomic::Ordering::Acquire),
 8326        1,
 8327        "Should not restart LSP server on a related LSP settings change that is the same"
 8328    );
 8329
 8330    update_test_project_settings(cx, |project_settings| {
 8331        project_settings.lsp.insert(
 8332            language_server_name.into(),
 8333            LspSettings {
 8334                binary: None,
 8335                settings: None,
 8336                initialization_options: None,
 8337            },
 8338        );
 8339    });
 8340    cx.executor().run_until_parked();
 8341    assert_eq!(
 8342        server_restarts.load(atomic::Ordering::Acquire),
 8343        2,
 8344        "Should restart LSP server on another related LSP settings change"
 8345    );
 8346}
 8347
 8348#[gpui::test]
 8349async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
 8350    init_test(cx, |_| {});
 8351
 8352    let mut cx = EditorLspTestContext::new_rust(
 8353        lsp::ServerCapabilities {
 8354            completion_provider: Some(lsp::CompletionOptions {
 8355                trigger_characters: Some(vec![".".to_string()]),
 8356                resolve_provider: Some(true),
 8357                ..Default::default()
 8358            }),
 8359            ..Default::default()
 8360        },
 8361        cx,
 8362    )
 8363    .await;
 8364
 8365    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
 8366    cx.simulate_keystroke(".");
 8367    let completion_item = lsp::CompletionItem {
 8368        label: "some".into(),
 8369        kind: Some(lsp::CompletionItemKind::SNIPPET),
 8370        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
 8371        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
 8372            kind: lsp::MarkupKind::Markdown,
 8373            value: "```rust\nSome(2)\n```".to_string(),
 8374        })),
 8375        deprecated: Some(false),
 8376        sort_text: Some("fffffff2".to_string()),
 8377        filter_text: Some("some".to_string()),
 8378        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 8379        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 8380            range: lsp::Range {
 8381                start: lsp::Position {
 8382                    line: 0,
 8383                    character: 22,
 8384                },
 8385                end: lsp::Position {
 8386                    line: 0,
 8387                    character: 22,
 8388                },
 8389            },
 8390            new_text: "Some(2)".to_string(),
 8391        })),
 8392        additional_text_edits: Some(vec![lsp::TextEdit {
 8393            range: lsp::Range {
 8394                start: lsp::Position {
 8395                    line: 0,
 8396                    character: 20,
 8397                },
 8398                end: lsp::Position {
 8399                    line: 0,
 8400                    character: 22,
 8401                },
 8402            },
 8403            new_text: "".to_string(),
 8404        }]),
 8405        ..Default::default()
 8406    };
 8407
 8408    let closure_completion_item = completion_item.clone();
 8409    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
 8410        let task_completion_item = closure_completion_item.clone();
 8411        async move {
 8412            Ok(Some(lsp::CompletionResponse::Array(vec![
 8413                task_completion_item,
 8414            ])))
 8415        }
 8416    });
 8417
 8418    request.next().await;
 8419
 8420    cx.condition(|editor, _| editor.context_menu_visible())
 8421        .await;
 8422    let apply_additional_edits = cx.update_editor(|editor, cx| {
 8423        editor
 8424            .confirm_completion(&ConfirmCompletion::default(), cx)
 8425            .unwrap()
 8426    });
 8427    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
 8428
 8429    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
 8430        let task_completion_item = completion_item.clone();
 8431        async move { Ok(task_completion_item) }
 8432    })
 8433    .next()
 8434    .await
 8435    .unwrap();
 8436    apply_additional_edits.await.unwrap();
 8437    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
 8438}
 8439
 8440#[gpui::test]
 8441async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
 8442    init_test(cx, |_| {});
 8443
 8444    let mut cx = EditorLspTestContext::new(
 8445        Language::new(
 8446            LanguageConfig {
 8447                matcher: LanguageMatcher {
 8448                    path_suffixes: vec!["jsx".into()],
 8449                    ..Default::default()
 8450                },
 8451                overrides: [(
 8452                    "element".into(),
 8453                    LanguageConfigOverride {
 8454                        word_characters: Override::Set(['-'].into_iter().collect()),
 8455                        ..Default::default()
 8456                    },
 8457                )]
 8458                .into_iter()
 8459                .collect(),
 8460                ..Default::default()
 8461            },
 8462            Some(tree_sitter_typescript::language_tsx()),
 8463        )
 8464        .with_override_query("(jsx_self_closing_element) @element")
 8465        .unwrap(),
 8466        lsp::ServerCapabilities {
 8467            completion_provider: Some(lsp::CompletionOptions {
 8468                trigger_characters: Some(vec![":".to_string()]),
 8469                ..Default::default()
 8470            }),
 8471            ..Default::default()
 8472        },
 8473        cx,
 8474    )
 8475    .await;
 8476
 8477    cx.lsp
 8478        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 8479            Ok(Some(lsp::CompletionResponse::Array(vec![
 8480                lsp::CompletionItem {
 8481                    label: "bg-blue".into(),
 8482                    ..Default::default()
 8483                },
 8484                lsp::CompletionItem {
 8485                    label: "bg-red".into(),
 8486                    ..Default::default()
 8487                },
 8488                lsp::CompletionItem {
 8489                    label: "bg-yellow".into(),
 8490                    ..Default::default()
 8491                },
 8492            ])))
 8493        });
 8494
 8495    cx.set_state(r#"<p class="bgˇ" />"#);
 8496
 8497    // Trigger completion when typing a dash, because the dash is an extra
 8498    // word character in the 'element' scope, which contains the cursor.
 8499    cx.simulate_keystroke("-");
 8500    cx.executor().run_until_parked();
 8501    cx.update_editor(|editor, _| {
 8502        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8503            assert_eq!(
 8504                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8505                &["bg-red", "bg-blue", "bg-yellow"]
 8506            );
 8507        } else {
 8508            panic!("expected completion menu to be open");
 8509        }
 8510    });
 8511
 8512    cx.simulate_keystroke("l");
 8513    cx.executor().run_until_parked();
 8514    cx.update_editor(|editor, _| {
 8515        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8516            assert_eq!(
 8517                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8518                &["bg-blue", "bg-yellow"]
 8519            );
 8520        } else {
 8521            panic!("expected completion menu to be open");
 8522        }
 8523    });
 8524
 8525    // When filtering completions, consider the character after the '-' to
 8526    // be the start of a subword.
 8527    cx.set_state(r#"<p class="yelˇ" />"#);
 8528    cx.simulate_keystroke("l");
 8529    cx.executor().run_until_parked();
 8530    cx.update_editor(|editor, _| {
 8531        if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
 8532            assert_eq!(
 8533                menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
 8534                &["bg-yellow"]
 8535            );
 8536        } else {
 8537            panic!("expected completion menu to be open");
 8538        }
 8539    });
 8540}
 8541
 8542#[gpui::test]
 8543async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
 8544    init_test(cx, |settings| {
 8545        settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
 8546    });
 8547
 8548    let fs = FakeFs::new(cx.executor());
 8549    fs.insert_file("/file.rs", Default::default()).await;
 8550
 8551    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 8552    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8553
 8554    language_registry.add(Arc::new(Language::new(
 8555        LanguageConfig {
 8556            name: "Rust".into(),
 8557            matcher: LanguageMatcher {
 8558                path_suffixes: vec!["rs".to_string()],
 8559                ..Default::default()
 8560            },
 8561            prettier_parser_name: Some("test_parser".to_string()),
 8562            ..Default::default()
 8563        },
 8564        Some(tree_sitter_rust::language()),
 8565    )));
 8566
 8567    let test_plugin = "test_plugin";
 8568    let _ = language_registry.register_fake_lsp_adapter(
 8569        "Rust",
 8570        FakeLspAdapter {
 8571            prettier_plugins: vec![test_plugin],
 8572            ..Default::default()
 8573        },
 8574    );
 8575
 8576    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
 8577    let buffer = project
 8578        .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
 8579        .await
 8580        .unwrap();
 8581
 8582    let buffer_text = "one\ntwo\nthree\n";
 8583    let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
 8584    let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
 8585    _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
 8586
 8587    editor
 8588        .update(cx, |editor, cx| {
 8589            editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 8590        })
 8591        .unwrap()
 8592        .await;
 8593    assert_eq!(
 8594        editor.update(cx, |editor, cx| editor.text(cx)),
 8595        buffer_text.to_string() + prettier_format_suffix,
 8596        "Test prettier formatting was not applied to the original buffer text",
 8597    );
 8598
 8599    update_test_language_settings(cx, |settings| {
 8600        settings.defaults.formatter = Some(language_settings::Formatter::Auto)
 8601    });
 8602    let format = editor.update(cx, |editor, cx| {
 8603        editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
 8604    });
 8605    format.await.unwrap();
 8606    assert_eq!(
 8607        editor.update(cx, |editor, cx| editor.text(cx)),
 8608        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
 8609        "Autoformatting (via test prettier) was not applied to the original buffer text",
 8610    );
 8611}
 8612
 8613#[gpui::test]
 8614async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
 8615    init_test(cx, |_| {});
 8616    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 8617    let base_text = indoc! {r#"struct Row;
 8618struct Row1;
 8619struct Row2;
 8620
 8621struct Row4;
 8622struct Row5;
 8623struct Row6;
 8624
 8625struct Row8;
 8626struct Row9;
 8627struct Row10;"#};
 8628
 8629    // When addition hunks are not adjacent to carets, no hunk revert is performed
 8630    assert_hunk_revert(
 8631        indoc! {r#"struct Row;
 8632                   struct Row1;
 8633                   struct Row1.1;
 8634                   struct Row1.2;
 8635                   struct Row2;ˇ
 8636
 8637                   struct Row4;
 8638                   struct Row5;
 8639                   struct Row6;
 8640
 8641                   struct Row8;
 8642                   ˇstruct Row9;
 8643                   struct Row9.1;
 8644                   struct Row9.2;
 8645                   struct Row9.3;
 8646                   struct Row10;"#},
 8647        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 8648        indoc! {r#"struct Row;
 8649                   struct Row1;
 8650                   struct Row1.1;
 8651                   struct Row1.2;
 8652                   struct Row2;ˇ
 8653
 8654                   struct Row4;
 8655                   struct Row5;
 8656                   struct Row6;
 8657
 8658                   struct Row8;
 8659                   ˇstruct Row9;
 8660                   struct Row9.1;
 8661                   struct Row9.2;
 8662                   struct Row9.3;
 8663                   struct Row10;"#},
 8664        base_text,
 8665        &mut cx,
 8666    );
 8667    // Same for selections
 8668    assert_hunk_revert(
 8669        indoc! {r#"struct Row;
 8670                   struct Row1;
 8671                   struct Row2;
 8672                   struct Row2.1;
 8673                   struct Row2.2;
 8674                   «ˇ
 8675                   struct Row4;
 8676                   struct» Row5;
 8677                   «struct Row6;
 8678                   ˇ»
 8679                   struct Row9.1;
 8680                   struct Row9.2;
 8681                   struct Row9.3;
 8682                   struct Row8;
 8683                   struct Row9;
 8684                   struct Row10;"#},
 8685        vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
 8686        indoc! {r#"struct Row;
 8687                   struct Row1;
 8688                   struct Row2;
 8689                   struct Row2.1;
 8690                   struct Row2.2;
 8691                   «ˇ
 8692                   struct Row4;
 8693                   struct» Row5;
 8694                   «struct Row6;
 8695                   ˇ»
 8696                   struct Row9.1;
 8697                   struct Row9.2;
 8698                   struct Row9.3;
 8699                   struct Row8;
 8700                   struct Row9;
 8701                   struct Row10;"#},
 8702        base_text,
 8703        &mut cx,
 8704    );
 8705
 8706    // When carets and selections intersect the addition hunks, those are reverted.
 8707    // Adjacent carets got merged.
 8708    assert_hunk_revert(
 8709        indoc! {r#"struct Row;
 8710                   ˇ// something on the top
 8711                   struct Row1;
 8712                   struct Row2;
 8713                   struct Roˇw3.1;
 8714                   struct Row2.2;
 8715                   struct Row2.3;ˇ
 8716
 8717                   struct Row4;
 8718                   struct ˇRow5.1;
 8719                   struct Row5.2;
 8720                   struct «Rowˇ»5.3;
 8721                   struct Row5;
 8722                   struct Row6;
 8723                   ˇ
 8724                   struct Row9.1;
 8725                   struct «Rowˇ»9.2;
 8726                   struct «ˇRow»9.3;
 8727                   struct Row8;
 8728                   struct Row9;
 8729                   «ˇ// something on bottom»
 8730                   struct Row10;"#},
 8731        vec![
 8732            DiffHunkStatus::Added,
 8733            DiffHunkStatus::Added,
 8734            DiffHunkStatus::Added,
 8735            DiffHunkStatus::Added,
 8736            DiffHunkStatus::Added,
 8737        ],
 8738        indoc! {r#"struct Row;
 8739                   ˇstruct Row1;
 8740                   struct Row2;
 8741                   ˇ
 8742                   struct Row4;
 8743                   ˇstruct Row5;
 8744                   struct Row6;
 8745                   ˇ
 8746                   ˇstruct Row8;
 8747                   struct Row9;
 8748                   ˇstruct Row10;"#},
 8749        base_text,
 8750        &mut cx,
 8751    );
 8752}
 8753
 8754#[gpui::test]
 8755async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
 8756    init_test(cx, |_| {});
 8757    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 8758    let base_text = indoc! {r#"struct Row;
 8759struct Row1;
 8760struct Row2;
 8761
 8762struct Row4;
 8763struct Row5;
 8764struct Row6;
 8765
 8766struct Row8;
 8767struct Row9;
 8768struct Row10;"#};
 8769
 8770    // Modification hunks behave the same as the addition ones.
 8771    assert_hunk_revert(
 8772        indoc! {r#"struct Row;
 8773                   struct Row1;
 8774                   struct Row33;
 8775                   ˇ
 8776                   struct Row4;
 8777                   struct Row5;
 8778                   struct Row6;
 8779                   ˇ
 8780                   struct Row99;
 8781                   struct Row9;
 8782                   struct Row10;"#},
 8783        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 8784        indoc! {r#"struct Row;
 8785                   struct Row1;
 8786                   struct Row33;
 8787                   ˇ
 8788                   struct Row4;
 8789                   struct Row5;
 8790                   struct Row6;
 8791                   ˇ
 8792                   struct Row99;
 8793                   struct Row9;
 8794                   struct Row10;"#},
 8795        base_text,
 8796        &mut cx,
 8797    );
 8798    assert_hunk_revert(
 8799        indoc! {r#"struct Row;
 8800                   struct Row1;
 8801                   struct Row33;
 8802                   «ˇ
 8803                   struct Row4;
 8804                   struct» Row5;
 8805                   «struct Row6;
 8806                   ˇ»
 8807                   struct Row99;
 8808                   struct Row9;
 8809                   struct Row10;"#},
 8810        vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
 8811        indoc! {r#"struct Row;
 8812                   struct Row1;
 8813                   struct Row33;
 8814                   «ˇ
 8815                   struct Row4;
 8816                   struct» Row5;
 8817                   «struct Row6;
 8818                   ˇ»
 8819                   struct Row99;
 8820                   struct Row9;
 8821                   struct Row10;"#},
 8822        base_text,
 8823        &mut cx,
 8824    );
 8825
 8826    assert_hunk_revert(
 8827        indoc! {r#"ˇstruct Row1.1;
 8828                   struct Row1;
 8829                   «ˇstr»uct Row22;
 8830
 8831                   struct ˇRow44;
 8832                   struct Row5;
 8833                   struct «Rˇ»ow66;ˇ
 8834
 8835                   «struˇ»ct Row88;
 8836                   struct Row9;
 8837                   struct Row1011;ˇ"#},
 8838        vec![
 8839            DiffHunkStatus::Modified,
 8840            DiffHunkStatus::Modified,
 8841            DiffHunkStatus::Modified,
 8842            DiffHunkStatus::Modified,
 8843            DiffHunkStatus::Modified,
 8844            DiffHunkStatus::Modified,
 8845        ],
 8846        indoc! {r#"struct Row;
 8847                   ˇstruct Row1;
 8848                   struct Row2;
 8849                   ˇ
 8850                   struct Row4;
 8851                   ˇstruct Row5;
 8852                   struct Row6;
 8853                   ˇ
 8854                   struct Row8;
 8855                   ˇstruct Row9;
 8856                   struct Row10;ˇ"#},
 8857        base_text,
 8858        &mut cx,
 8859    );
 8860}
 8861
 8862#[gpui::test]
 8863async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
 8864    init_test(cx, |_| {});
 8865    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 8866    let base_text = indoc! {r#"struct Row;
 8867struct Row1;
 8868struct Row2;
 8869
 8870struct Row4;
 8871struct Row5;
 8872struct Row6;
 8873
 8874struct Row8;
 8875struct Row9;
 8876struct Row10;"#};
 8877
 8878    // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
 8879    assert_hunk_revert(
 8880        indoc! {r#"struct Row;
 8881                   struct Row2;
 8882
 8883                   ˇstruct Row4;
 8884                   struct Row5;
 8885                   struct Row6;
 8886                   ˇ
 8887                   struct Row8;
 8888                   struct Row10;"#},
 8889        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 8890        indoc! {r#"struct Row;
 8891                   struct Row2;
 8892
 8893                   ˇstruct Row4;
 8894                   struct Row5;
 8895                   struct Row6;
 8896                   ˇ
 8897                   struct Row8;
 8898                   struct Row10;"#},
 8899        base_text,
 8900        &mut cx,
 8901    );
 8902    assert_hunk_revert(
 8903        indoc! {r#"struct Row;
 8904                   struct Row2;
 8905
 8906                   «ˇstruct Row4;
 8907                   struct» Row5;
 8908                   «struct Row6;
 8909                   ˇ»
 8910                   struct Row8;
 8911                   struct Row10;"#},
 8912        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 8913        indoc! {r#"struct Row;
 8914                   struct Row2;
 8915
 8916                   «ˇstruct Row4;
 8917                   struct» Row5;
 8918                   «struct Row6;
 8919                   ˇ»
 8920                   struct Row8;
 8921                   struct Row10;"#},
 8922        base_text,
 8923        &mut cx,
 8924    );
 8925
 8926    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
 8927    assert_hunk_revert(
 8928        indoc! {r#"struct Row;
 8929                   ˇstruct Row2;
 8930
 8931                   struct Row4;
 8932                   struct Row5;
 8933                   struct Row6;
 8934
 8935                   struct Row8;ˇ
 8936                   struct Row10;"#},
 8937        vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
 8938        indoc! {r#"struct Row;
 8939                   struct Row1;
 8940                   ˇstruct Row2;
 8941
 8942                   struct Row4;
 8943                   struct Row5;
 8944                   struct Row6;
 8945
 8946                   struct Row8;ˇ
 8947                   struct Row9;
 8948                   struct Row10;"#},
 8949        base_text,
 8950        &mut cx,
 8951    );
 8952    assert_hunk_revert(
 8953        indoc! {r#"struct Row;
 8954                   struct Row2«ˇ;
 8955                   struct Row4;
 8956                   struct» Row5;
 8957                   «struct Row6;
 8958
 8959                   struct Row8;ˇ»
 8960                   struct Row10;"#},
 8961        vec![
 8962            DiffHunkStatus::Removed,
 8963            DiffHunkStatus::Removed,
 8964            DiffHunkStatus::Removed,
 8965        ],
 8966        indoc! {r#"struct Row;
 8967                   struct Row1;
 8968                   struct Row2«ˇ;
 8969
 8970                   struct Row4;
 8971                   struct» Row5;
 8972                   «struct Row6;
 8973
 8974                   struct Row8;ˇ»
 8975                   struct Row9;
 8976                   struct Row10;"#},
 8977        base_text,
 8978        &mut cx,
 8979    );
 8980}
 8981
 8982#[gpui::test]
 8983async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
 8984    init_test(cx, |_| {});
 8985
 8986    let cols = 4;
 8987    let rows = 10;
 8988    let sample_text_1 = sample_text(rows, cols, 'a');
 8989    assert_eq!(
 8990        sample_text_1,
 8991        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 8992    );
 8993    let sample_text_2 = sample_text(rows, cols, 'l');
 8994    assert_eq!(
 8995        sample_text_2,
 8996        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 8997    );
 8998    let sample_text_3 = sample_text(rows, cols, 'v');
 8999    assert_eq!(
 9000        sample_text_3,
 9001        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9002    );
 9003
 9004    fn diff_every_buffer_row(
 9005        buffer: &Model<Buffer>,
 9006        sample_text: String,
 9007        cols: usize,
 9008        cx: &mut gpui::TestAppContext,
 9009    ) {
 9010        // revert first character in each row, creating one large diff hunk per buffer
 9011        let is_first_char = |offset: usize| offset % cols == 0;
 9012        buffer.update(cx, |buffer, cx| {
 9013            buffer.set_text(
 9014                sample_text
 9015                    .chars()
 9016                    .enumerate()
 9017                    .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
 9018                    .collect::<String>(),
 9019                cx,
 9020            );
 9021            buffer.set_diff_base(Some(sample_text), cx);
 9022        });
 9023        cx.executor().run_until_parked();
 9024    }
 9025
 9026    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9027    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9028
 9029    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9030    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9031
 9032    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9033    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9034
 9035    let multibuffer = cx.new_model(|cx| {
 9036        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9037        multibuffer.push_excerpts(
 9038            buffer_1.clone(),
 9039            [
 9040                ExcerptRange {
 9041                    context: Point::new(0, 0)..Point::new(3, 0),
 9042                    primary: None,
 9043                },
 9044                ExcerptRange {
 9045                    context: Point::new(5, 0)..Point::new(7, 0),
 9046                    primary: None,
 9047                },
 9048                ExcerptRange {
 9049                    context: Point::new(9, 0)..Point::new(10, 4),
 9050                    primary: None,
 9051                },
 9052            ],
 9053            cx,
 9054        );
 9055        multibuffer.push_excerpts(
 9056            buffer_2.clone(),
 9057            [
 9058                ExcerptRange {
 9059                    context: Point::new(0, 0)..Point::new(3, 0),
 9060                    primary: None,
 9061                },
 9062                ExcerptRange {
 9063                    context: Point::new(5, 0)..Point::new(7, 0),
 9064                    primary: None,
 9065                },
 9066                ExcerptRange {
 9067                    context: Point::new(9, 0)..Point::new(10, 4),
 9068                    primary: None,
 9069                },
 9070            ],
 9071            cx,
 9072        );
 9073        multibuffer.push_excerpts(
 9074            buffer_3.clone(),
 9075            [
 9076                ExcerptRange {
 9077                    context: Point::new(0, 0)..Point::new(3, 0),
 9078                    primary: None,
 9079                },
 9080                ExcerptRange {
 9081                    context: Point::new(5, 0)..Point::new(7, 0),
 9082                    primary: None,
 9083                },
 9084                ExcerptRange {
 9085                    context: Point::new(9, 0)..Point::new(10, 4),
 9086                    primary: None,
 9087                },
 9088            ],
 9089            cx,
 9090        );
 9091        multibuffer
 9092    });
 9093
 9094    let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
 9095    editor.update(cx, |editor, cx| {
 9096        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");
 9097        editor.select_all(&SelectAll, cx);
 9098        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9099    });
 9100    cx.executor().run_until_parked();
 9101    // When all ranges are selected, all buffer hunks are reverted.
 9102    editor.update(cx, |editor, cx| {
 9103        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");
 9104    });
 9105    buffer_1.update(cx, |buffer, _| {
 9106        assert_eq!(buffer.text(), sample_text_1);
 9107    });
 9108    buffer_2.update(cx, |buffer, _| {
 9109        assert_eq!(buffer.text(), sample_text_2);
 9110    });
 9111    buffer_3.update(cx, |buffer, _| {
 9112        assert_eq!(buffer.text(), sample_text_3);
 9113    });
 9114
 9115    diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
 9116    diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
 9117    diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
 9118    editor.update(cx, |editor, cx| {
 9119        editor.change_selections(None, cx, |s| {
 9120            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
 9121        });
 9122        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
 9123    });
 9124    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
 9125    // but not affect buffer_2 and its related excerpts.
 9126    editor.update(cx, |editor, cx| {
 9127        assert_eq!(
 9128            editor.text(cx),
 9129            "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"
 9130        );
 9131    });
 9132    buffer_1.update(cx, |buffer, _| {
 9133        assert_eq!(buffer.text(), sample_text_1);
 9134    });
 9135    buffer_2.update(cx, |buffer, _| {
 9136        assert_eq!(
 9137            buffer.text(),
 9138            "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
 9139        );
 9140    });
 9141    buffer_3.update(cx, |buffer, _| {
 9142        assert_eq!(
 9143            buffer.text(),
 9144            "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
 9145        );
 9146    });
 9147}
 9148
 9149#[gpui::test]
 9150async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
 9151    init_test(cx, |_| {});
 9152
 9153    let cols = 4;
 9154    let rows = 10;
 9155    let sample_text_1 = sample_text(rows, cols, 'a');
 9156    assert_eq!(
 9157        sample_text_1,
 9158        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9159    );
 9160    let sample_text_2 = sample_text(rows, cols, 'l');
 9161    assert_eq!(
 9162        sample_text_2,
 9163        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9164    );
 9165    let sample_text_3 = sample_text(rows, cols, 'v');
 9166    assert_eq!(
 9167        sample_text_3,
 9168        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9169    );
 9170
 9171    let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
 9172    let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
 9173    let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
 9174
 9175    let multi_buffer = cx.new_model(|cx| {
 9176        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
 9177        multibuffer.push_excerpts(
 9178            buffer_1.clone(),
 9179            [
 9180                ExcerptRange {
 9181                    context: Point::new(0, 0)..Point::new(3, 0),
 9182                    primary: None,
 9183                },
 9184                ExcerptRange {
 9185                    context: Point::new(5, 0)..Point::new(7, 0),
 9186                    primary: None,
 9187                },
 9188                ExcerptRange {
 9189                    context: Point::new(9, 0)..Point::new(10, 4),
 9190                    primary: None,
 9191                },
 9192            ],
 9193            cx,
 9194        );
 9195        multibuffer.push_excerpts(
 9196            buffer_2.clone(),
 9197            [
 9198                ExcerptRange {
 9199                    context: Point::new(0, 0)..Point::new(3, 0),
 9200                    primary: None,
 9201                },
 9202                ExcerptRange {
 9203                    context: Point::new(5, 0)..Point::new(7, 0),
 9204                    primary: None,
 9205                },
 9206                ExcerptRange {
 9207                    context: Point::new(9, 0)..Point::new(10, 4),
 9208                    primary: None,
 9209                },
 9210            ],
 9211            cx,
 9212        );
 9213        multibuffer.push_excerpts(
 9214            buffer_3.clone(),
 9215            [
 9216                ExcerptRange {
 9217                    context: Point::new(0, 0)..Point::new(3, 0),
 9218                    primary: None,
 9219                },
 9220                ExcerptRange {
 9221                    context: Point::new(5, 0)..Point::new(7, 0),
 9222                    primary: None,
 9223                },
 9224                ExcerptRange {
 9225                    context: Point::new(9, 0)..Point::new(10, 4),
 9226                    primary: None,
 9227                },
 9228            ],
 9229            cx,
 9230        );
 9231        multibuffer
 9232    });
 9233
 9234    let fs = FakeFs::new(cx.executor());
 9235    fs.insert_tree(
 9236        "/a",
 9237        json!({
 9238            "main.rs": sample_text_1,
 9239            "other.rs": sample_text_2,
 9240            "lib.rs": sample_text_3,
 9241        }),
 9242    )
 9243    .await;
 9244    let project = Project::test(fs, ["/a".as_ref()], cx).await;
 9245    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 9246    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9247    let multi_buffer_editor =
 9248        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
 9249    let multibuffer_item_id = workspace
 9250        .update(cx, |workspace, cx| {
 9251            assert!(
 9252                workspace.active_item(cx).is_none(),
 9253                "active item should be None before the first item is added"
 9254            );
 9255            workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
 9256            let active_item = workspace
 9257                .active_item(cx)
 9258                .expect("should have an active item after adding the multi buffer");
 9259            assert!(
 9260                !active_item.is_singleton(cx),
 9261                "A multi buffer was expected to active after adding"
 9262            );
 9263            active_item.item_id()
 9264        })
 9265        .unwrap();
 9266    cx.executor().run_until_parked();
 9267
 9268    multi_buffer_editor.update(cx, |editor, cx| {
 9269        editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
 9270        editor.open_excerpts(&OpenExcerpts, cx);
 9271    });
 9272    cx.executor().run_until_parked();
 9273    let first_item_id = workspace
 9274        .update(cx, |workspace, cx| {
 9275            let active_item = workspace
 9276                .active_item(cx)
 9277                .expect("should have an active item after navigating into the 1st buffer");
 9278            let first_item_id = active_item.item_id();
 9279            assert_ne!(
 9280                first_item_id, multibuffer_item_id,
 9281                "Should navigate into the 1st buffer and activate it"
 9282            );
 9283            assert!(
 9284                active_item.is_singleton(cx),
 9285                "New active item should be a singleton buffer"
 9286            );
 9287            assert_eq!(
 9288                active_item
 9289                    .act_as::<Editor>(cx)
 9290                    .expect("should have navigated into an editor for the 1st buffer")
 9291                    .read(cx)
 9292                    .text(cx),
 9293                sample_text_1
 9294            );
 9295
 9296            workspace
 9297                .go_back(workspace.active_pane().downgrade(), cx)
 9298                .detach_and_log_err(cx);
 9299
 9300            first_item_id
 9301        })
 9302        .unwrap();
 9303    cx.executor().run_until_parked();
 9304    workspace
 9305        .update(cx, |workspace, cx| {
 9306            let active_item = workspace
 9307                .active_item(cx)
 9308                .expect("should have an active item after navigating back");
 9309            assert_eq!(
 9310                active_item.item_id(),
 9311                multibuffer_item_id,
 9312                "Should navigate back to the multi buffer"
 9313            );
 9314            assert!(!active_item.is_singleton(cx));
 9315        })
 9316        .unwrap();
 9317
 9318    multi_buffer_editor.update(cx, |editor, cx| {
 9319        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9320            s.select_ranges(Some(39..40))
 9321        });
 9322        editor.open_excerpts(&OpenExcerpts, cx);
 9323    });
 9324    cx.executor().run_until_parked();
 9325    let second_item_id = workspace
 9326        .update(cx, |workspace, cx| {
 9327            let active_item = workspace
 9328                .active_item(cx)
 9329                .expect("should have an active item after navigating into the 2nd buffer");
 9330            let second_item_id = active_item.item_id();
 9331            assert_ne!(
 9332                second_item_id, multibuffer_item_id,
 9333                "Should navigate away from the multibuffer"
 9334            );
 9335            assert_ne!(
 9336                second_item_id, first_item_id,
 9337                "Should navigate into the 2nd buffer and activate it"
 9338            );
 9339            assert!(
 9340                active_item.is_singleton(cx),
 9341                "New active item should be a singleton buffer"
 9342            );
 9343            assert_eq!(
 9344                active_item
 9345                    .act_as::<Editor>(cx)
 9346                    .expect("should have navigated into an editor")
 9347                    .read(cx)
 9348                    .text(cx),
 9349                sample_text_2
 9350            );
 9351
 9352            workspace
 9353                .go_back(workspace.active_pane().downgrade(), cx)
 9354                .detach_and_log_err(cx);
 9355
 9356            second_item_id
 9357        })
 9358        .unwrap();
 9359    cx.executor().run_until_parked();
 9360    workspace
 9361        .update(cx, |workspace, cx| {
 9362            let active_item = workspace
 9363                .active_item(cx)
 9364                .expect("should have an active item after navigating back from the 2nd buffer");
 9365            assert_eq!(
 9366                active_item.item_id(),
 9367                multibuffer_item_id,
 9368                "Should navigate back from the 2nd buffer to the multi buffer"
 9369            );
 9370            assert!(!active_item.is_singleton(cx));
 9371        })
 9372        .unwrap();
 9373
 9374    multi_buffer_editor.update(cx, |editor, cx| {
 9375        editor.change_selections(Some(Autoscroll::Next), cx, |s| {
 9376            s.select_ranges(Some(60..70))
 9377        });
 9378        editor.open_excerpts(&OpenExcerpts, cx);
 9379    });
 9380    cx.executor().run_until_parked();
 9381    workspace
 9382        .update(cx, |workspace, cx| {
 9383            let active_item = workspace
 9384                .active_item(cx)
 9385                .expect("should have an active item after navigating into the 3rd buffer");
 9386            let third_item_id = active_item.item_id();
 9387            assert_ne!(
 9388                third_item_id, multibuffer_item_id,
 9389                "Should navigate into the 3rd buffer and activate it"
 9390            );
 9391            assert_ne!(third_item_id, first_item_id);
 9392            assert_ne!(third_item_id, second_item_id);
 9393            assert!(
 9394                active_item.is_singleton(cx),
 9395                "New active item should be a singleton buffer"
 9396            );
 9397            assert_eq!(
 9398                active_item
 9399                    .act_as::<Editor>(cx)
 9400                    .expect("should have navigated into an editor")
 9401                    .read(cx)
 9402                    .text(cx),
 9403                sample_text_3
 9404            );
 9405
 9406            workspace
 9407                .go_back(workspace.active_pane().downgrade(), cx)
 9408                .detach_and_log_err(cx);
 9409        })
 9410        .unwrap();
 9411    cx.executor().run_until_parked();
 9412    workspace
 9413        .update(cx, |workspace, cx| {
 9414            let active_item = workspace
 9415                .active_item(cx)
 9416                .expect("should have an active item after navigating back from the 3rd buffer");
 9417            assert_eq!(
 9418                active_item.item_id(),
 9419                multibuffer_item_id,
 9420                "Should navigate back from the 3rd buffer to the multi buffer"
 9421            );
 9422            assert!(!active_item.is_singleton(cx));
 9423        })
 9424        .unwrap();
 9425}
 9426
 9427#[gpui::test]
 9428async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 9429    init_test(cx, |_| {});
 9430
 9431    let mut cx = EditorTestContext::new(cx).await;
 9432
 9433    let diff_base = r#"
 9434        use some::mod;
 9435
 9436        const A: u32 = 42;
 9437
 9438        fn main() {
 9439            println!("hello");
 9440
 9441            println!("world");
 9442        }
 9443        "#
 9444    .unindent();
 9445
 9446    cx.set_state(
 9447        &r#"
 9448        use some::modified;
 9449
 9450        ˇ
 9451        fn main() {
 9452            println!("hello there");
 9453
 9454            println!("around the");
 9455            println!("world");
 9456        }
 9457        "#
 9458        .unindent(),
 9459    );
 9460
 9461    cx.set_diff_base(Some(&diff_base));
 9462    executor.run_until_parked();
 9463    let unexpanded_hunks = vec![
 9464        (
 9465            "use some::mod;\n".to_string(),
 9466            DiffHunkStatus::Modified,
 9467            0..1,
 9468        ),
 9469        (
 9470            "const A: u32 = 42;\n".to_string(),
 9471            DiffHunkStatus::Removed,
 9472            2..2,
 9473        ),
 9474        (
 9475            "    println!(\"hello\");\n".to_string(),
 9476            DiffHunkStatus::Modified,
 9477            4..5,
 9478        ),
 9479        ("".to_string(), DiffHunkStatus::Added, 6..7),
 9480    ];
 9481    cx.update_editor(|editor, cx| {
 9482        let snapshot = editor.snapshot(cx);
 9483        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9484        assert_eq!(all_hunks, unexpanded_hunks);
 9485    });
 9486
 9487    cx.update_editor(|editor, cx| {
 9488        for _ in 0..4 {
 9489            editor.go_to_hunk(&GoToHunk, cx);
 9490            editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
 9491        }
 9492    });
 9493    executor.run_until_parked();
 9494    cx.assert_editor_state(
 9495        &r#"
 9496        use some::modified;
 9497
 9498        ˇ
 9499        fn main() {
 9500            println!("hello there");
 9501
 9502            println!("around the");
 9503            println!("world");
 9504        }
 9505        "#
 9506        .unindent(),
 9507    );
 9508    cx.update_editor(|editor, cx| {
 9509        let snapshot = editor.snapshot(cx);
 9510        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9511        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9512        assert_eq!(
 9513            expanded_hunks_background_highlights(editor, &snapshot),
 9514            vec![1..2, 7..8, 9..10],
 9515            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
 9516        );
 9517        assert_eq!(
 9518            all_hunks,
 9519            vec![
 9520                ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, 1..2),
 9521                ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, 4..4),
 9522                ("    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, 7..8),
 9523                ("".to_string(), DiffHunkStatus::Added, 9..10),
 9524            ],
 9525            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
 9526            (from modified and removed hunks)"
 9527        );
 9528        assert_eq!(
 9529            all_hunks, all_expanded_hunks,
 9530            "Editor hunks should not change and all be expanded"
 9531        );
 9532    });
 9533
 9534    cx.update_editor(|editor, cx| {
 9535        editor.cancel(&Cancel, cx);
 9536
 9537        let snapshot = editor.snapshot(cx);
 9538        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9539        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
 9540        assert_eq!(
 9541            expanded_hunks_background_highlights(editor, &snapshot),
 9542            Vec::new(),
 9543            "After cancelling in editor, no git highlights should be left"
 9544        );
 9545        assert_eq!(
 9546            all_expanded_hunks,
 9547            Vec::new(),
 9548            "After cancelling in editor, no hunks should be expanded"
 9549        );
 9550        assert_eq!(
 9551            all_hunks, unexpanded_hunks,
 9552            "After cancelling in editor, regular hunks' coordinates should get back to normal"
 9553        );
 9554    });
 9555}
 9556
 9557#[gpui::test]
 9558async fn test_toggled_diff_base_change(
 9559    executor: BackgroundExecutor,
 9560    cx: &mut gpui::TestAppContext,
 9561) {
 9562    init_test(cx, |_| {});
 9563
 9564    let mut cx = EditorTestContext::new(cx).await;
 9565
 9566    let diff_base = r#"
 9567        use some::mod1;
 9568        use some::mod2;
 9569
 9570        const A: u32 = 42;
 9571        const B: u32 = 42;
 9572        const C: u32 = 42;
 9573
 9574        fn main(ˇ) {
 9575            println!("hello");
 9576
 9577            println!("world");
 9578        }
 9579        "#
 9580    .unindent();
 9581
 9582    cx.set_state(
 9583        &r#"
 9584        use some::mod2;
 9585
 9586        const A: u32 = 42;
 9587        const C: u32 = 42;
 9588
 9589        fn main(ˇ) {
 9590            //println!("hello");
 9591
 9592            println!("world");
 9593            //
 9594            //
 9595        }
 9596        "#
 9597        .unindent(),
 9598    );
 9599
 9600    cx.set_diff_base(Some(&diff_base));
 9601    executor.run_until_parked();
 9602    cx.update_editor(|editor, cx| {
 9603        let snapshot = editor.snapshot(cx);
 9604        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9605        assert_eq!(
 9606            all_hunks,
 9607            vec![
 9608                (
 9609                    "use some::mod1;\n".to_string(),
 9610                    DiffHunkStatus::Removed,
 9611                    0..0
 9612                ),
 9613                (
 9614                    "const B: u32 = 42;\n".to_string(),
 9615                    DiffHunkStatus::Removed,
 9616                    3..3
 9617                ),
 9618                (
 9619                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9620                    DiffHunkStatus::Modified,
 9621                    5..7
 9622                ),
 9623                ("".to_string(), DiffHunkStatus::Added, 9..11),
 9624            ]
 9625        );
 9626    });
 9627
 9628    cx.update_editor(|editor, cx| {
 9629        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
 9630    });
 9631    executor.run_until_parked();
 9632    cx.assert_editor_state(
 9633        &r#"
 9634        use some::mod2;
 9635
 9636        const A: u32 = 42;
 9637        const C: u32 = 42;
 9638
 9639        fn main(ˇ) {
 9640            //println!("hello");
 9641
 9642            println!("world");
 9643            //
 9644            //
 9645        }
 9646        "#
 9647        .unindent(),
 9648    );
 9649    cx.update_editor(|editor, cx| {
 9650        let snapshot = editor.snapshot(cx);
 9651        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9652        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9653        assert_eq!(
 9654            expanded_hunks_background_highlights(editor, &snapshot),
 9655            vec![9..11, 13..15],
 9656            "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
 9657        );
 9658        assert_eq!(
 9659            all_hunks,
 9660            vec![
 9661                ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, 1..1),
 9662                ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, 5..5),
 9663                ("fn main(ˇ) {\n    println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, 9..11),
 9664                ("".to_string(), DiffHunkStatus::Added, 13..15),
 9665            ],
 9666            "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
 9667            (from modified and removed hunks)"
 9668        );
 9669        assert_eq!(
 9670            all_hunks, all_expanded_hunks,
 9671            "Editor hunks should not change and all be expanded"
 9672        );
 9673    });
 9674
 9675    cx.set_diff_base(Some("new diff base!"));
 9676    executor.run_until_parked();
 9677
 9678    cx.update_editor(|editor, cx| {
 9679        let snapshot = editor.snapshot(cx);
 9680        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9681        let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
 9682        assert_eq!(
 9683            expanded_hunks_background_highlights(editor, &snapshot),
 9684            Vec::new(),
 9685            "After diff base is changed, old git highlights should be removed"
 9686        );
 9687        assert_eq!(
 9688            all_expanded_hunks,
 9689            Vec::new(),
 9690            "After diff base is changed, old git hunk expansions should be removed"
 9691        );
 9692        assert_eq!(
 9693            all_hunks,
 9694            vec![(
 9695                "new diff base!".to_string(),
 9696                DiffHunkStatus::Modified,
 9697                0..snapshot.display_snapshot.max_point().row()
 9698            )],
 9699            "After diff base is changed, hunks should update"
 9700        );
 9701    });
 9702}
 9703
 9704#[gpui::test]
 9705async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
 9706    init_test(cx, |_| {});
 9707
 9708    let mut cx = EditorTestContext::new(cx).await;
 9709
 9710    let diff_base = r#"
 9711        use some::mod1;
 9712        use some::mod2;
 9713
 9714        const A: u32 = 42;
 9715        const B: u32 = 42;
 9716        const C: u32 = 42;
 9717
 9718        fn main(ˇ) {
 9719            println!("hello");
 9720
 9721            println!("world");
 9722        }
 9723
 9724        fn another() {
 9725            println!("another");
 9726        }
 9727
 9728        fn another2() {
 9729            println!("another2");
 9730        }
 9731        "#
 9732    .unindent();
 9733
 9734    cx.set_state(
 9735        &r#"
 9736        «use some::mod2;
 9737
 9738        const A: u32 = 42;
 9739        const C: u32 = 42;
 9740
 9741        fn main() {
 9742            //println!("hello");
 9743
 9744            println!("world");
 9745            //
 9746            //ˇ»
 9747        }
 9748
 9749        fn another() {
 9750            println!("another");
 9751            println!("another");
 9752        }
 9753
 9754            println!("another2");
 9755        }
 9756        "#
 9757        .unindent(),
 9758    );
 9759
 9760    cx.set_diff_base(Some(&diff_base));
 9761    executor.run_until_parked();
 9762    cx.update_editor(|editor, cx| {
 9763        let snapshot = editor.snapshot(cx);
 9764        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9765        assert_eq!(
 9766            all_hunks,
 9767            vec![
 9768                (
 9769                    "use some::mod1;\n".to_string(),
 9770                    DiffHunkStatus::Removed,
 9771                    0..0
 9772                ),
 9773                (
 9774                    "const B: u32 = 42;\n".to_string(),
 9775                    DiffHunkStatus::Removed,
 9776                    3..3
 9777                ),
 9778                (
 9779                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9780                    DiffHunkStatus::Modified,
 9781                    5..7
 9782                ),
 9783                ("".to_string(), DiffHunkStatus::Added, 9..11),
 9784                ("".to_string(), DiffHunkStatus::Added, 15..16),
 9785                (
 9786                    "fn another2() {\n".to_string(),
 9787                    DiffHunkStatus::Removed,
 9788                    18..18
 9789                ),
 9790            ]
 9791        );
 9792    });
 9793
 9794    cx.update_editor(|editor, cx| {
 9795        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
 9796    });
 9797    executor.run_until_parked();
 9798    cx.assert_editor_state(
 9799        &r#"
 9800        «use some::mod2;
 9801
 9802        const A: u32 = 42;
 9803        const C: u32 = 42;
 9804
 9805        fn main() {
 9806            //println!("hello");
 9807
 9808            println!("world");
 9809            //
 9810            //ˇ»
 9811        }
 9812
 9813        fn another() {
 9814            println!("another");
 9815            println!("another");
 9816        }
 9817
 9818            println!("another2");
 9819        }
 9820        "#
 9821        .unindent(),
 9822    );
 9823    cx.update_editor(|editor, cx| {
 9824        let snapshot = editor.snapshot(cx);
 9825        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9826        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9827        assert_eq!(
 9828            expanded_hunks_background_highlights(editor, &snapshot),
 9829            vec![9..11, 13..15, 19..20]
 9830        );
 9831        assert_eq!(
 9832            all_hunks,
 9833            vec![
 9834                (
 9835                    "use some::mod1;\n".to_string(),
 9836                    DiffHunkStatus::Removed,
 9837                    1..1
 9838                ),
 9839                (
 9840                    "const B: u32 = 42;\n".to_string(),
 9841                    DiffHunkStatus::Removed,
 9842                    5..5
 9843                ),
 9844                (
 9845                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9846                    DiffHunkStatus::Modified,
 9847                    9..11
 9848                ),
 9849                ("".to_string(), DiffHunkStatus::Added, 13..15),
 9850                ("".to_string(), DiffHunkStatus::Added, 19..20),
 9851                (
 9852                    "fn another2() {\n".to_string(),
 9853                    DiffHunkStatus::Removed,
 9854                    23..23
 9855                ),
 9856            ],
 9857        );
 9858        assert_eq!(all_hunks, all_expanded_hunks);
 9859    });
 9860
 9861    cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
 9862    cx.executor().run_until_parked();
 9863    cx.assert_editor_state(
 9864        &r#"
 9865        «use some::mod2;
 9866
 9867        const A: u32 = 42;
 9868        const C: u32 = 42;
 9869
 9870        fn main() {
 9871            //println!("hello");
 9872
 9873            println!("world");
 9874            //
 9875            //ˇ»
 9876        }
 9877
 9878        fn another() {
 9879            println!("another");
 9880            println!("another");
 9881        }
 9882
 9883            println!("another2");
 9884        }
 9885        "#
 9886        .unindent(),
 9887    );
 9888    cx.update_editor(|editor, cx| {
 9889        let snapshot = editor.snapshot(cx);
 9890        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9891        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9892        assert_eq!(
 9893            expanded_hunks_background_highlights(editor, &snapshot),
 9894            vec![5..6],
 9895            "Only one hunk is left not folded, its highlight should be visible"
 9896        );
 9897        assert_eq!(
 9898            all_hunks,
 9899            vec![
 9900                (
 9901                    "use some::mod1;\n".to_string(),
 9902                    DiffHunkStatus::Removed,
 9903                    0..0
 9904                ),
 9905                (
 9906                    "const B: u32 = 42;\n".to_string(),
 9907                    DiffHunkStatus::Removed,
 9908                    0..0
 9909                ),
 9910                (
 9911                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9912                    DiffHunkStatus::Modified,
 9913                    0..0
 9914                ),
 9915                ("".to_string(), DiffHunkStatus::Added, 0..1),
 9916                ("".to_string(), DiffHunkStatus::Added, 5..6),
 9917                (
 9918                    "fn another2() {\n".to_string(),
 9919                    DiffHunkStatus::Removed,
 9920                    9..9
 9921                ),
 9922            ],
 9923            "Hunk list should still return shifted folded hunks"
 9924        );
 9925        assert_eq!(
 9926            all_expanded_hunks,
 9927            vec![
 9928                ("".to_string(), DiffHunkStatus::Added, 5..6),
 9929                (
 9930                    "fn another2() {\n".to_string(),
 9931                    DiffHunkStatus::Removed,
 9932                    9..9
 9933                ),
 9934            ],
 9935            "Only non-folded hunks should be left expanded"
 9936        );
 9937    });
 9938
 9939    cx.update_editor(|editor, cx| {
 9940        editor.select_all(&SelectAll, cx);
 9941        editor.unfold_lines(&UnfoldLines, cx);
 9942    });
 9943    cx.executor().run_until_parked();
 9944    cx.assert_editor_state(
 9945        &r#"
 9946        «use some::mod2;
 9947
 9948        const A: u32 = 42;
 9949        const C: u32 = 42;
 9950
 9951        fn main() {
 9952            //println!("hello");
 9953
 9954            println!("world");
 9955            //
 9956            //
 9957        }
 9958
 9959        fn another() {
 9960            println!("another");
 9961            println!("another");
 9962        }
 9963
 9964            println!("another2");
 9965        }
 9966        ˇ»"#
 9967        .unindent(),
 9968    );
 9969    cx.update_editor(|editor, cx| {
 9970        let snapshot = editor.snapshot(cx);
 9971        let all_hunks = editor_hunks(editor, &snapshot, cx);
 9972        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
 9973        assert_eq!(
 9974            expanded_hunks_background_highlights(editor, &snapshot),
 9975            vec![9..11, 13..15, 19..20],
 9976            "After unfolding, all hunk diffs should be visible again"
 9977        );
 9978        assert_eq!(
 9979            all_hunks,
 9980            vec![
 9981                (
 9982                    "use some::mod1;\n".to_string(),
 9983                    DiffHunkStatus::Removed,
 9984                    1..1
 9985                ),
 9986                (
 9987                    "const B: u32 = 42;\n".to_string(),
 9988                    DiffHunkStatus::Removed,
 9989                    5..5
 9990                ),
 9991                (
 9992                    "fn main(ˇ) {\n    println!(\"hello\");\n".to_string(),
 9993                    DiffHunkStatus::Modified,
 9994                    9..11
 9995                ),
 9996                ("".to_string(), DiffHunkStatus::Added, 13..15),
 9997                ("".to_string(), DiffHunkStatus::Added, 19..20),
 9998                (
 9999                    "fn another2() {\n".to_string(),
10000                    DiffHunkStatus::Removed,
10001                    23..23
10002                ),
10003            ],
10004        );
10005        assert_eq!(all_hunks, all_expanded_hunks);
10006    });
10007}
10008
10009#[gpui::test]
10010async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10011    init_test(cx, |_| {});
10012
10013    let cols = 4;
10014    let rows = 10;
10015    let sample_text_1 = sample_text(rows, cols, 'a');
10016    assert_eq!(
10017        sample_text_1,
10018        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10019    );
10020    let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10021    let sample_text_2 = sample_text(rows, cols, 'l');
10022    assert_eq!(
10023        sample_text_2,
10024        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10025    );
10026    let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10027    let sample_text_3 = sample_text(rows, cols, 'v');
10028    assert_eq!(
10029        sample_text_3,
10030        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10031    );
10032    let modified_sample_text_3 =
10033        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10034    let buffer_1 = cx.new_model(|cx| {
10035        let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10036        buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10037        buffer
10038    });
10039    let buffer_2 = cx.new_model(|cx| {
10040        let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10041        buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10042        buffer
10043    });
10044    let buffer_3 = cx.new_model(|cx| {
10045        let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10046        buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10047        buffer
10048    });
10049
10050    let multi_buffer = cx.new_model(|cx| {
10051        let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10052        multibuffer.push_excerpts(
10053            buffer_1.clone(),
10054            [
10055                ExcerptRange {
10056                    context: Point::new(0, 0)..Point::new(3, 0),
10057                    primary: None,
10058                },
10059                ExcerptRange {
10060                    context: Point::new(5, 0)..Point::new(7, 0),
10061                    primary: None,
10062                },
10063                ExcerptRange {
10064                    context: Point::new(9, 0)..Point::new(10, 4),
10065                    primary: None,
10066                },
10067            ],
10068            cx,
10069        );
10070        multibuffer.push_excerpts(
10071            buffer_2.clone(),
10072            [
10073                ExcerptRange {
10074                    context: Point::new(0, 0)..Point::new(3, 0),
10075                    primary: None,
10076                },
10077                ExcerptRange {
10078                    context: Point::new(5, 0)..Point::new(7, 0),
10079                    primary: None,
10080                },
10081                ExcerptRange {
10082                    context: Point::new(9, 0)..Point::new(10, 4),
10083                    primary: None,
10084                },
10085            ],
10086            cx,
10087        );
10088        multibuffer.push_excerpts(
10089            buffer_3.clone(),
10090            [
10091                ExcerptRange {
10092                    context: Point::new(0, 0)..Point::new(3, 0),
10093                    primary: None,
10094                },
10095                ExcerptRange {
10096                    context: Point::new(5, 0)..Point::new(7, 0),
10097                    primary: None,
10098                },
10099                ExcerptRange {
10100                    context: Point::new(9, 0)..Point::new(10, 4),
10101                    primary: None,
10102                },
10103            ],
10104            cx,
10105        );
10106        multibuffer
10107    });
10108
10109    let fs = FakeFs::new(cx.executor());
10110    fs.insert_tree(
10111        "/a",
10112        json!({
10113            "main.rs": modified_sample_text_1,
10114            "other.rs": modified_sample_text_2,
10115            "lib.rs": modified_sample_text_3,
10116        }),
10117    )
10118    .await;
10119
10120    let project = Project::test(fs, ["/a".as_ref()], cx).await;
10121    let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10122    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10123    let multi_buffer_editor =
10124        cx.new_view(|cx| Editor::new(EditorMode::Full, multi_buffer, Some(project.clone()), cx));
10125    cx.executor().run_until_parked();
10126
10127    let expected_all_hunks = vec![
10128        ("bbbb\n".to_string(), DiffHunkStatus::Removed, 3..3),
10129        ("nnnn\n".to_string(), DiffHunkStatus::Modified, 16..17),
10130        ("".to_string(), DiffHunkStatus::Added, 31..32),
10131    ];
10132    let expected_all_hunks_shifted = vec![
10133        ("bbbb\n".to_string(), DiffHunkStatus::Removed, 4..4),
10134        ("nnnn\n".to_string(), DiffHunkStatus::Modified, 18..19),
10135        ("".to_string(), DiffHunkStatus::Added, 33..34),
10136    ];
10137
10138    multi_buffer_editor.update(cx, |editor, cx| {
10139        let snapshot = editor.snapshot(cx);
10140        let all_hunks = editor_hunks(editor, &snapshot, cx);
10141        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10142        assert_eq!(
10143            expanded_hunks_background_highlights(editor, &snapshot),
10144            Vec::new(),
10145        );
10146        assert_eq!(all_hunks, expected_all_hunks);
10147        assert_eq!(all_expanded_hunks, Vec::new());
10148    });
10149
10150    multi_buffer_editor.update(cx, |editor, cx| {
10151        editor.select_all(&SelectAll, cx);
10152        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10153    });
10154    cx.executor().run_until_parked();
10155    multi_buffer_editor.update(cx, |editor, cx| {
10156        let snapshot = editor.snapshot(cx);
10157        let all_hunks = editor_hunks(editor, &snapshot, cx);
10158        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10159        assert_eq!(
10160            expanded_hunks_background_highlights(editor, &snapshot),
10161            vec![18..19, 33..34],
10162        );
10163        assert_eq!(all_hunks, expected_all_hunks_shifted);
10164        assert_eq!(all_hunks, all_expanded_hunks);
10165    });
10166
10167    multi_buffer_editor.update(cx, |editor, cx| {
10168        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10169    });
10170    cx.executor().run_until_parked();
10171    multi_buffer_editor.update(cx, |editor, cx| {
10172        let snapshot = editor.snapshot(cx);
10173        let all_hunks = editor_hunks(editor, &snapshot, cx);
10174        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10175        assert_eq!(
10176            expanded_hunks_background_highlights(editor, &snapshot),
10177            Vec::new(),
10178        );
10179        assert_eq!(all_hunks, expected_all_hunks);
10180        assert_eq!(all_expanded_hunks, Vec::new());
10181    });
10182
10183    multi_buffer_editor.update(cx, |editor, cx| {
10184        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10185    });
10186    cx.executor().run_until_parked();
10187    multi_buffer_editor.update(cx, |editor, cx| {
10188        let snapshot = editor.snapshot(cx);
10189        let all_hunks = editor_hunks(editor, &snapshot, cx);
10190        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10191        assert_eq!(
10192            expanded_hunks_background_highlights(editor, &snapshot),
10193            vec![18..19, 33..34],
10194        );
10195        assert_eq!(all_hunks, expected_all_hunks_shifted);
10196        assert_eq!(all_hunks, all_expanded_hunks);
10197    });
10198
10199    multi_buffer_editor.update(cx, |editor, cx| {
10200        editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10201    });
10202    cx.executor().run_until_parked();
10203    multi_buffer_editor.update(cx, |editor, cx| {
10204        let snapshot = editor.snapshot(cx);
10205        let all_hunks = editor_hunks(editor, &snapshot, cx);
10206        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10207        assert_eq!(
10208            expanded_hunks_background_highlights(editor, &snapshot),
10209            Vec::new(),
10210        );
10211        assert_eq!(all_hunks, expected_all_hunks);
10212        assert_eq!(all_expanded_hunks, Vec::new());
10213    });
10214}
10215
10216#[gpui::test]
10217async fn test_edits_around_toggled_additions(
10218    executor: BackgroundExecutor,
10219    cx: &mut gpui::TestAppContext,
10220) {
10221    init_test(cx, |_| {});
10222
10223    let mut cx = EditorTestContext::new(cx).await;
10224
10225    let diff_base = r#"
10226        use some::mod1;
10227        use some::mod2;
10228
10229        const A: u32 = 42;
10230
10231        fn main() {
10232            println!("hello");
10233
10234            println!("world");
10235        }
10236        "#
10237    .unindent();
10238    executor.run_until_parked();
10239    cx.set_state(
10240        &r#"
10241        use some::mod1;
10242        use some::mod2;
10243
10244        const A: u32 = 42;
10245        const B: u32 = 42;
10246        const C: u32 = 42;
10247        ˇ
10248
10249        fn main() {
10250            println!("hello");
10251
10252            println!("world");
10253        }
10254        "#
10255        .unindent(),
10256    );
10257
10258    cx.set_diff_base(Some(&diff_base));
10259    executor.run_until_parked();
10260    cx.update_editor(|editor, cx| {
10261        let snapshot = editor.snapshot(cx);
10262        let all_hunks = editor_hunks(editor, &snapshot, cx);
10263        assert_eq!(
10264            all_hunks,
10265            vec![("".to_string(), DiffHunkStatus::Added, 4..7)]
10266        );
10267    });
10268    cx.update_editor(|editor, cx| {
10269        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10270    });
10271    executor.run_until_parked();
10272    cx.assert_editor_state(
10273        &r#"
10274        use some::mod1;
10275        use some::mod2;
10276
10277        const A: u32 = 42;
10278        const B: u32 = 42;
10279        const C: u32 = 42;
10280        ˇ
10281
10282        fn main() {
10283            println!("hello");
10284
10285            println!("world");
10286        }
10287        "#
10288        .unindent(),
10289    );
10290    cx.update_editor(|editor, cx| {
10291        let snapshot = editor.snapshot(cx);
10292        let all_hunks = editor_hunks(editor, &snapshot, cx);
10293        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10294        assert_eq!(
10295            all_hunks,
10296            vec![("".to_string(), DiffHunkStatus::Added, 4..7)]
10297        );
10298        assert_eq!(
10299            expanded_hunks_background_highlights(editor, &snapshot),
10300            vec![4..7]
10301        );
10302        assert_eq!(all_hunks, all_expanded_hunks);
10303    });
10304
10305    cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10306    executor.run_until_parked();
10307    cx.assert_editor_state(
10308        &r#"
10309        use some::mod1;
10310        use some::mod2;
10311
10312        const A: u32 = 42;
10313        const B: u32 = 42;
10314        const C: u32 = 42;
10315        const D: u32 = 42;
10316        ˇ
10317
10318        fn main() {
10319            println!("hello");
10320
10321            println!("world");
10322        }
10323        "#
10324        .unindent(),
10325    );
10326    cx.update_editor(|editor, cx| {
10327        let snapshot = editor.snapshot(cx);
10328        let all_hunks = editor_hunks(editor, &snapshot, cx);
10329        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10330        assert_eq!(
10331            all_hunks,
10332            vec![("".to_string(), DiffHunkStatus::Added, 4..8)]
10333        );
10334        assert_eq!(
10335            expanded_hunks_background_highlights(editor, &snapshot),
10336            vec![4..8],
10337            "Edited hunk should have one more line added"
10338        );
10339        assert_eq!(
10340            all_hunks, all_expanded_hunks,
10341            "Expanded hunk should also grow with the addition"
10342        );
10343    });
10344
10345    cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
10346    executor.run_until_parked();
10347    cx.assert_editor_state(
10348        &r#"
10349        use some::mod1;
10350        use some::mod2;
10351
10352        const A: u32 = 42;
10353        const B: u32 = 42;
10354        const C: u32 = 42;
10355        const D: u32 = 42;
10356        const E: u32 = 42;
10357        ˇ
10358
10359        fn main() {
10360            println!("hello");
10361
10362            println!("world");
10363        }
10364        "#
10365        .unindent(),
10366    );
10367    cx.update_editor(|editor, cx| {
10368        let snapshot = editor.snapshot(cx);
10369        let all_hunks = editor_hunks(editor, &snapshot, cx);
10370        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10371        assert_eq!(
10372            all_hunks,
10373            vec![("".to_string(), DiffHunkStatus::Added, 4..9)]
10374        );
10375        assert_eq!(
10376            expanded_hunks_background_highlights(editor, &snapshot),
10377            vec![4..9],
10378            "Edited hunk should have one more line added"
10379        );
10380        assert_eq!(all_hunks, all_expanded_hunks);
10381    });
10382
10383    cx.update_editor(|editor, cx| {
10384        editor.move_up(&MoveUp, cx);
10385        editor.delete_line(&DeleteLine, cx);
10386    });
10387    executor.run_until_parked();
10388    cx.assert_editor_state(
10389        &r#"
10390        use some::mod1;
10391        use some::mod2;
10392
10393        const A: u32 = 42;
10394        const B: u32 = 42;
10395        const C: u32 = 42;
10396        const D: u32 = 42;
10397        ˇ
10398
10399        fn main() {
10400            println!("hello");
10401
10402            println!("world");
10403        }
10404        "#
10405        .unindent(),
10406    );
10407    cx.update_editor(|editor, cx| {
10408        let snapshot = editor.snapshot(cx);
10409        let all_hunks = editor_hunks(editor, &snapshot, cx);
10410        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10411        assert_eq!(
10412            all_hunks,
10413            vec![("".to_string(), DiffHunkStatus::Added, 4..8)]
10414        );
10415        assert_eq!(
10416            expanded_hunks_background_highlights(editor, &snapshot),
10417            vec![4..8],
10418            "Deleting a line should shrint the hunk"
10419        );
10420        assert_eq!(
10421            all_hunks, all_expanded_hunks,
10422            "Expanded hunk should also shrink with the addition"
10423        );
10424    });
10425
10426    cx.update_editor(|editor, cx| {
10427        editor.move_up(&MoveUp, cx);
10428        editor.delete_line(&DeleteLine, cx);
10429        editor.move_up(&MoveUp, cx);
10430        editor.delete_line(&DeleteLine, cx);
10431        editor.move_up(&MoveUp, cx);
10432        editor.delete_line(&DeleteLine, cx);
10433    });
10434    executor.run_until_parked();
10435    cx.assert_editor_state(
10436        &r#"
10437        use some::mod1;
10438        use some::mod2;
10439
10440        const A: u32 = 42;
10441        ˇ
10442
10443        fn main() {
10444            println!("hello");
10445
10446            println!("world");
10447        }
10448        "#
10449        .unindent(),
10450    );
10451    cx.update_editor(|editor, cx| {
10452        let snapshot = editor.snapshot(cx);
10453        let all_hunks = editor_hunks(editor, &snapshot, cx);
10454        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10455        assert_eq!(
10456            all_hunks,
10457            vec![("".to_string(), DiffHunkStatus::Added, 5..6)]
10458        );
10459        assert_eq!(
10460            expanded_hunks_background_highlights(editor, &snapshot),
10461            vec![5..6]
10462        );
10463        assert_eq!(all_hunks, all_expanded_hunks);
10464    });
10465
10466    cx.update_editor(|editor, cx| {
10467        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
10468        editor.delete_line(&DeleteLine, cx);
10469    });
10470    executor.run_until_parked();
10471    cx.assert_editor_state(
10472        &r#"
10473        ˇ
10474
10475        fn main() {
10476            println!("hello");
10477
10478            println!("world");
10479        }
10480        "#
10481        .unindent(),
10482    );
10483    cx.update_editor(|editor, cx| {
10484        let snapshot = editor.snapshot(cx);
10485        let all_hunks = editor_hunks(editor, &snapshot, cx);
10486        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10487        assert_eq!(
10488            all_hunks,
10489            vec![
10490                (
10491                    "use some::mod1;\nuse some::mod2;\n".to_string(),
10492                    DiffHunkStatus::Removed,
10493                    0..0
10494                ),
10495                (
10496                    "const A: u32 = 42;\n".to_string(),
10497                    DiffHunkStatus::Removed,
10498                    2..2
10499                )
10500            ]
10501        );
10502        assert_eq!(
10503            expanded_hunks_background_highlights(editor, &snapshot),
10504            Vec::new(),
10505            "Should close all stale expanded addition hunks"
10506        );
10507        assert_eq!(
10508            all_expanded_hunks,
10509            vec![(
10510                "const A: u32 = 42;\n".to_string(),
10511                DiffHunkStatus::Removed,
10512                2..2
10513            )],
10514            "Should open hunks that were adjacent to the stale addition one"
10515        );
10516    });
10517}
10518
10519#[gpui::test]
10520async fn test_edits_around_toggled_deletions(
10521    executor: BackgroundExecutor,
10522    cx: &mut gpui::TestAppContext,
10523) {
10524    init_test(cx, |_| {});
10525
10526    let mut cx = EditorTestContext::new(cx).await;
10527
10528    let diff_base = r#"
10529        use some::mod1;
10530        use some::mod2;
10531
10532        const A: u32 = 42;
10533        const B: u32 = 42;
10534        const C: u32 = 42;
10535
10536
10537        fn main() {
10538            println!("hello");
10539
10540            println!("world");
10541        }
10542        "#
10543    .unindent();
10544    executor.run_until_parked();
10545    cx.set_state(
10546        &r#"
10547        use some::mod1;
10548        use some::mod2;
10549
10550        ˇconst B: u32 = 42;
10551        const C: u32 = 42;
10552
10553
10554        fn main() {
10555            println!("hello");
10556
10557            println!("world");
10558        }
10559        "#
10560        .unindent(),
10561    );
10562
10563    cx.set_diff_base(Some(&diff_base));
10564    executor.run_until_parked();
10565    cx.update_editor(|editor, cx| {
10566        let snapshot = editor.snapshot(cx);
10567        let all_hunks = editor_hunks(editor, &snapshot, cx);
10568        assert_eq!(
10569            all_hunks,
10570            vec![(
10571                "const A: u32 = 42;\n".to_string(),
10572                DiffHunkStatus::Removed,
10573                3..3
10574            )]
10575        );
10576    });
10577    cx.update_editor(|editor, cx| {
10578        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10579    });
10580    executor.run_until_parked();
10581    cx.assert_editor_state(
10582        &r#"
10583        use some::mod1;
10584        use some::mod2;
10585
10586        ˇconst B: u32 = 42;
10587        const C: u32 = 42;
10588
10589
10590        fn main() {
10591            println!("hello");
10592
10593            println!("world");
10594        }
10595        "#
10596        .unindent(),
10597    );
10598    cx.update_editor(|editor, cx| {
10599        let snapshot = editor.snapshot(cx);
10600        let all_hunks = editor_hunks(editor, &snapshot, cx);
10601        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10602        assert_eq!(
10603            expanded_hunks_background_highlights(editor, &snapshot),
10604            Vec::new()
10605        );
10606        assert_eq!(
10607            all_hunks,
10608            vec![(
10609                "const A: u32 = 42;\n".to_string(),
10610                DiffHunkStatus::Removed,
10611                4..4
10612            )]
10613        );
10614        assert_eq!(all_hunks, all_expanded_hunks);
10615    });
10616
10617    cx.update_editor(|editor, cx| {
10618        editor.delete_line(&DeleteLine, cx);
10619    });
10620    executor.run_until_parked();
10621    cx.assert_editor_state(
10622        &r#"
10623        use some::mod1;
10624        use some::mod2;
10625
10626        ˇconst C: u32 = 42;
10627
10628
10629        fn main() {
10630            println!("hello");
10631
10632            println!("world");
10633        }
10634        "#
10635        .unindent(),
10636    );
10637    cx.update_editor(|editor, cx| {
10638        let snapshot = editor.snapshot(cx);
10639        let all_hunks = editor_hunks(editor, &snapshot, cx);
10640        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10641        assert_eq!(
10642            expanded_hunks_background_highlights(editor, &snapshot),
10643            Vec::new(),
10644            "Deleted hunks do not highlight current editor's background"
10645        );
10646        assert_eq!(
10647            all_hunks,
10648            vec![(
10649                "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
10650                DiffHunkStatus::Removed,
10651                5..5
10652            )]
10653        );
10654        assert_eq!(all_hunks, all_expanded_hunks);
10655    });
10656
10657    cx.update_editor(|editor, cx| {
10658        editor.delete_line(&DeleteLine, cx);
10659    });
10660    executor.run_until_parked();
10661    cx.assert_editor_state(
10662        &r#"
10663        use some::mod1;
10664        use some::mod2;
10665
10666        ˇ
10667
10668        fn main() {
10669            println!("hello");
10670
10671            println!("world");
10672        }
10673        "#
10674        .unindent(),
10675    );
10676    cx.update_editor(|editor, cx| {
10677        let snapshot = editor.snapshot(cx);
10678        let all_hunks = editor_hunks(editor, &snapshot, cx);
10679        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10680        assert_eq!(
10681            expanded_hunks_background_highlights(editor, &snapshot),
10682            Vec::new()
10683        );
10684        assert_eq!(
10685            all_hunks,
10686            vec![(
10687                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10688                DiffHunkStatus::Removed,
10689                6..6
10690            )]
10691        );
10692        assert_eq!(all_hunks, all_expanded_hunks);
10693    });
10694
10695    cx.update_editor(|editor, cx| {
10696        editor.handle_input("replacement", cx);
10697    });
10698    executor.run_until_parked();
10699    cx.assert_editor_state(
10700        &r#"
10701        use some::mod1;
10702        use some::mod2;
10703
10704        replacementˇ
10705
10706        fn main() {
10707            println!("hello");
10708
10709            println!("world");
10710        }
10711        "#
10712        .unindent(),
10713    );
10714    cx.update_editor(|editor, cx| {
10715        let snapshot = editor.snapshot(cx);
10716        let all_hunks = editor_hunks(editor, &snapshot, cx);
10717        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10718        assert_eq!(
10719            all_hunks,
10720            vec![(
10721                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
10722                DiffHunkStatus::Modified,
10723                7..8
10724            )]
10725        );
10726        assert_eq!(
10727            expanded_hunks_background_highlights(editor, &snapshot),
10728            vec![7..8],
10729            "Modified expanded hunks should display additions and highlight their background"
10730        );
10731        assert_eq!(all_hunks, all_expanded_hunks);
10732    });
10733}
10734
10735#[gpui::test]
10736async fn test_edits_around_toggled_modifications(
10737    executor: BackgroundExecutor,
10738    cx: &mut gpui::TestAppContext,
10739) {
10740    init_test(cx, |_| {});
10741
10742    let mut cx = EditorTestContext::new(cx).await;
10743
10744    let diff_base = r#"
10745        use some::mod1;
10746        use some::mod2;
10747
10748        const A: u32 = 42;
10749        const B: u32 = 42;
10750        const C: u32 = 42;
10751        const D: u32 = 42;
10752
10753
10754        fn main() {
10755            println!("hello");
10756
10757            println!("world");
10758        }"#
10759    .unindent();
10760    executor.run_until_parked();
10761    cx.set_state(
10762        &r#"
10763        use some::mod1;
10764        use some::mod2;
10765
10766        const A: u32 = 42;
10767        const B: u32 = 42;
10768        const C: u32 = 43ˇ
10769        const D: u32 = 42;
10770
10771
10772        fn main() {
10773            println!("hello");
10774
10775            println!("world");
10776        }"#
10777        .unindent(),
10778    );
10779
10780    cx.set_diff_base(Some(&diff_base));
10781    executor.run_until_parked();
10782    cx.update_editor(|editor, cx| {
10783        let snapshot = editor.snapshot(cx);
10784        let all_hunks = editor_hunks(editor, &snapshot, cx);
10785        assert_eq!(
10786            all_hunks,
10787            vec![(
10788                "const C: u32 = 42;\n".to_string(),
10789                DiffHunkStatus::Modified,
10790                5..6
10791            )]
10792        );
10793    });
10794    cx.update_editor(|editor, cx| {
10795        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10796    });
10797    executor.run_until_parked();
10798    cx.assert_editor_state(
10799        &r#"
10800        use some::mod1;
10801        use some::mod2;
10802
10803        const A: u32 = 42;
10804        const B: u32 = 42;
10805        const C: u32 = 43ˇ
10806        const D: u32 = 42;
10807
10808
10809        fn main() {
10810            println!("hello");
10811
10812            println!("world");
10813        }"#
10814        .unindent(),
10815    );
10816    cx.update_editor(|editor, cx| {
10817        let snapshot = editor.snapshot(cx);
10818        let all_hunks = editor_hunks(editor, &snapshot, cx);
10819        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10820        assert_eq!(
10821            expanded_hunks_background_highlights(editor, &snapshot),
10822            vec![6..7],
10823        );
10824        assert_eq!(
10825            all_hunks,
10826            vec![(
10827                "const C: u32 = 42;\n".to_string(),
10828                DiffHunkStatus::Modified,
10829                6..7
10830            )]
10831        );
10832        assert_eq!(all_hunks, all_expanded_hunks);
10833    });
10834
10835    cx.update_editor(|editor, cx| {
10836        editor.handle_input("\nnew_line\n", cx);
10837    });
10838    executor.run_until_parked();
10839    cx.assert_editor_state(
10840        &r#"
10841            use some::mod1;
10842            use some::mod2;
10843
10844            const A: u32 = 42;
10845            const B: u32 = 42;
10846            const C: u32 = 43
10847            new_line
10848            ˇ
10849            const D: u32 = 42;
10850
10851
10852            fn main() {
10853                println!("hello");
10854
10855                println!("world");
10856            }"#
10857        .unindent(),
10858    );
10859    cx.update_editor(|editor, cx| {
10860        let snapshot = editor.snapshot(cx);
10861        let all_hunks = editor_hunks(editor, &snapshot, cx);
10862        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10863        assert_eq!(
10864            expanded_hunks_background_highlights(editor, &snapshot),
10865            vec![6..9],
10866            "Modified hunk should grow highlighted lines on more text additions"
10867        );
10868        assert_eq!(
10869            all_hunks,
10870            vec![(
10871                "const C: u32 = 42;\n".to_string(),
10872                DiffHunkStatus::Modified,
10873                6..9
10874            )]
10875        );
10876        assert_eq!(all_hunks, all_expanded_hunks);
10877    });
10878
10879    cx.update_editor(|editor, cx| {
10880        editor.move_up(&MoveUp, cx);
10881        editor.move_up(&MoveUp, cx);
10882        editor.move_up(&MoveUp, cx);
10883        editor.delete_line(&DeleteLine, cx);
10884    });
10885    executor.run_until_parked();
10886    cx.assert_editor_state(
10887        &r#"
10888            use some::mod1;
10889            use some::mod2;
10890
10891            const A: u32 = 42;
10892            ˇconst C: u32 = 43
10893            new_line
10894
10895            const D: u32 = 42;
10896
10897
10898            fn main() {
10899                println!("hello");
10900
10901                println!("world");
10902            }"#
10903        .unindent(),
10904    );
10905    cx.update_editor(|editor, cx| {
10906        let snapshot = editor.snapshot(cx);
10907        let all_hunks = editor_hunks(editor, &snapshot, cx);
10908        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10909        assert_eq!(
10910            expanded_hunks_background_highlights(editor, &snapshot),
10911            vec![6..9],
10912            "Modified hunk should grow deleted lines on text deletions above"
10913        );
10914        assert_eq!(
10915            all_hunks,
10916            vec![(
10917                "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10918                DiffHunkStatus::Modified,
10919                6..9
10920            )]
10921        );
10922        assert_eq!(all_hunks, all_expanded_hunks);
10923    });
10924
10925    cx.update_editor(|editor, cx| {
10926        editor.move_up(&MoveUp, cx);
10927        editor.handle_input("v", cx);
10928    });
10929    executor.run_until_parked();
10930    cx.assert_editor_state(
10931        &r#"
10932            use some::mod1;
10933            use some::mod2;
10934
10935            vˇconst A: u32 = 42;
10936            const C: u32 = 43
10937            new_line
10938
10939            const D: u32 = 42;
10940
10941
10942            fn main() {
10943                println!("hello");
10944
10945                println!("world");
10946            }"#
10947        .unindent(),
10948    );
10949    cx.update_editor(|editor, cx| {
10950        let snapshot = editor.snapshot(cx);
10951        let all_hunks = editor_hunks(editor, &snapshot, cx);
10952        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10953        assert_eq!(
10954            expanded_hunks_background_highlights(editor, &snapshot),
10955            vec![6..10],
10956            "Modified hunk should grow deleted lines on text modifications above"
10957        );
10958        assert_eq!(
10959            all_hunks,
10960            vec![(
10961                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
10962                DiffHunkStatus::Modified,
10963                6..10
10964            )]
10965        );
10966        assert_eq!(all_hunks, all_expanded_hunks);
10967    });
10968
10969    cx.update_editor(|editor, cx| {
10970        editor.move_down(&MoveDown, cx);
10971        editor.move_down(&MoveDown, cx);
10972        editor.delete_line(&DeleteLine, cx)
10973    });
10974    executor.run_until_parked();
10975    cx.assert_editor_state(
10976        &r#"
10977            use some::mod1;
10978            use some::mod2;
10979
10980            vconst A: u32 = 42;
10981            const C: u32 = 43
10982            ˇ
10983            const D: u32 = 42;
10984
10985
10986            fn main() {
10987                println!("hello");
10988
10989                println!("world");
10990            }"#
10991        .unindent(),
10992    );
10993    cx.update_editor(|editor, cx| {
10994        let snapshot = editor.snapshot(cx);
10995        let all_hunks = editor_hunks(editor, &snapshot, cx);
10996        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10997        assert_eq!(
10998            expanded_hunks_background_highlights(editor, &snapshot),
10999            vec![6..9],
11000            "Modified hunk should grow shrink lines on modification lines removal"
11001        );
11002        assert_eq!(
11003            all_hunks,
11004            vec![(
11005                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11006                DiffHunkStatus::Modified,
11007                6..9
11008            )]
11009        );
11010        assert_eq!(all_hunks, all_expanded_hunks);
11011    });
11012
11013    cx.update_editor(|editor, cx| {
11014        editor.move_up(&MoveUp, cx);
11015        editor.move_up(&MoveUp, cx);
11016        editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11017        editor.delete_line(&DeleteLine, cx)
11018    });
11019    executor.run_until_parked();
11020    cx.assert_editor_state(
11021        &r#"
11022            use some::mod1;
11023            use some::mod2;
11024
11025            ˇ
11026
11027            fn main() {
11028                println!("hello");
11029
11030                println!("world");
11031            }"#
11032        .unindent(),
11033    );
11034    cx.update_editor(|editor, cx| {
11035        let snapshot = editor.snapshot(cx);
11036        let all_hunks = editor_hunks(editor, &snapshot, cx);
11037        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11038        assert_eq!(
11039            expanded_hunks_background_highlights(editor, &snapshot),
11040            Vec::new(),
11041            "Modified hunk should turn into a removed one on all modified lines removal"
11042        );
11043        assert_eq!(
11044            all_hunks,
11045            vec![(
11046                "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11047                    .to_string(),
11048                DiffHunkStatus::Removed,
11049                7..7
11050            )]
11051        );
11052        assert_eq!(all_hunks, all_expanded_hunks);
11053    });
11054}
11055
11056#[gpui::test]
11057async fn test_multiple_expanded_hunks_merge(
11058    executor: BackgroundExecutor,
11059    cx: &mut gpui::TestAppContext,
11060) {
11061    init_test(cx, |_| {});
11062
11063    let mut cx = EditorTestContext::new(cx).await;
11064
11065    let diff_base = r#"
11066        use some::mod1;
11067        use some::mod2;
11068
11069        const A: u32 = 42;
11070        const B: u32 = 42;
11071        const C: u32 = 42;
11072        const D: u32 = 42;
11073
11074
11075        fn main() {
11076            println!("hello");
11077
11078            println!("world");
11079        }"#
11080    .unindent();
11081    executor.run_until_parked();
11082    cx.set_state(
11083        &r#"
11084        use some::mod1;
11085        use some::mod2;
11086
11087        const A: u32 = 42;
11088        const B: u32 = 42;
11089        const C: u32 = 43ˇ
11090        const D: u32 = 42;
11091
11092
11093        fn main() {
11094            println!("hello");
11095
11096            println!("world");
11097        }"#
11098        .unindent(),
11099    );
11100
11101    cx.set_diff_base(Some(&diff_base));
11102    executor.run_until_parked();
11103    cx.update_editor(|editor, cx| {
11104        let snapshot = editor.snapshot(cx);
11105        let all_hunks = editor_hunks(editor, &snapshot, cx);
11106        assert_eq!(
11107            all_hunks,
11108            vec![(
11109                "const C: u32 = 42;\n".to_string(),
11110                DiffHunkStatus::Modified,
11111                5..6
11112            )]
11113        );
11114    });
11115    cx.update_editor(|editor, cx| {
11116        editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11117    });
11118    executor.run_until_parked();
11119    cx.assert_editor_state(
11120        &r#"
11121        use some::mod1;
11122        use some::mod2;
11123
11124        const A: u32 = 42;
11125        const B: u32 = 42;
11126        const C: u32 = 43ˇ
11127        const D: u32 = 42;
11128
11129
11130        fn main() {
11131            println!("hello");
11132
11133            println!("world");
11134        }"#
11135        .unindent(),
11136    );
11137    cx.update_editor(|editor, cx| {
11138        let snapshot = editor.snapshot(cx);
11139        let all_hunks = editor_hunks(editor, &snapshot, cx);
11140        let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11141        assert_eq!(
11142            expanded_hunks_background_highlights(editor, &snapshot),
11143            vec![6..7],
11144        );
11145        assert_eq!(
11146            all_hunks,
11147            vec![(
11148                "const C: u32 = 42;\n".to_string(),
11149                DiffHunkStatus::Modified,
11150                6..7
11151            )]
11152        );
11153        assert_eq!(all_hunks, all_expanded_hunks);
11154    });
11155
11156    cx.update_editor(|editor, cx| {
11157        editor.handle_input("\nnew_line\n", cx);
11158    });
11159    executor.run_until_parked();
11160    cx.assert_editor_state(
11161        &r#"
11162            use some::mod1;
11163            use some::mod2;
11164
11165            const A: u32 = 42;
11166            const B: u32 = 42;
11167            const C: u32 = 43
11168            new_line
11169            ˇ
11170            const D: u32 = 42;
11171
11172
11173            fn main() {
11174                println!("hello");
11175
11176                println!("world");
11177            }"#
11178        .unindent(),
11179    );
11180}
11181
11182fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
11183    let point = DisplayPoint::new(row as u32, column as u32);
11184    point..point
11185}
11186
11187fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
11188    let (text, ranges) = marked_text_ranges(marked_text, true);
11189    assert_eq!(view.text(cx), text);
11190    assert_eq!(
11191        view.selections.ranges(cx),
11192        ranges,
11193        "Assert selections are {}",
11194        marked_text
11195    );
11196}
11197
11198/// Handle completion request passing a marked string specifying where the completion
11199/// should be triggered from using '|' character, what range should be replaced, and what completions
11200/// should be returned using '<' and '>' to delimit the range
11201pub fn handle_completion_request(
11202    cx: &mut EditorLspTestContext,
11203    marked_string: &str,
11204    completions: Vec<&'static str>,
11205) -> impl Future<Output = ()> {
11206    let complete_from_marker: TextRangeMarker = '|'.into();
11207    let replace_range_marker: TextRangeMarker = ('<', '>').into();
11208    let (_, mut marked_ranges) = marked_text_ranges_by(
11209        marked_string,
11210        vec![complete_from_marker.clone(), replace_range_marker.clone()],
11211    );
11212
11213    let complete_from_position =
11214        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
11215    let replace_range =
11216        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
11217
11218    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
11219        let completions = completions.clone();
11220        async move {
11221            assert_eq!(params.text_document_position.text_document.uri, url.clone());
11222            assert_eq!(
11223                params.text_document_position.position,
11224                complete_from_position
11225            );
11226            Ok(Some(lsp::CompletionResponse::Array(
11227                completions
11228                    .iter()
11229                    .map(|completion_text| lsp::CompletionItem {
11230                        label: completion_text.to_string(),
11231                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11232                            range: replace_range,
11233                            new_text: completion_text.to_string(),
11234                        })),
11235                        ..Default::default()
11236                    })
11237                    .collect(),
11238            )))
11239        }
11240    });
11241
11242    async move {
11243        request.next().await;
11244    }
11245}
11246
11247fn handle_resolve_completion_request(
11248    cx: &mut EditorLspTestContext,
11249    edits: Option<Vec<(&'static str, &'static str)>>,
11250) -> impl Future<Output = ()> {
11251    let edits = edits.map(|edits| {
11252        edits
11253            .iter()
11254            .map(|(marked_string, new_text)| {
11255                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
11256                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
11257                lsp::TextEdit::new(replace_range, new_text.to_string())
11258            })
11259            .collect::<Vec<_>>()
11260    });
11261
11262    let mut request =
11263        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11264            let edits = edits.clone();
11265            async move {
11266                Ok(lsp::CompletionItem {
11267                    additional_text_edits: edits,
11268                    ..Default::default()
11269                })
11270            }
11271        });
11272
11273    async move {
11274        request.next().await;
11275    }
11276}
11277
11278pub(crate) fn update_test_language_settings(
11279    cx: &mut TestAppContext,
11280    f: impl Fn(&mut AllLanguageSettingsContent),
11281) {
11282    _ = cx.update(|cx| {
11283        cx.update_global(|store: &mut SettingsStore, cx| {
11284            store.update_user_settings::<AllLanguageSettings>(cx, f);
11285        });
11286    });
11287}
11288
11289pub(crate) fn update_test_project_settings(
11290    cx: &mut TestAppContext,
11291    f: impl Fn(&mut ProjectSettings),
11292) {
11293    _ = cx.update(|cx| {
11294        cx.update_global(|store: &mut SettingsStore, cx| {
11295            store.update_user_settings::<ProjectSettings>(cx, f);
11296        });
11297    });
11298}
11299
11300pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
11301    _ = cx.update(|cx| {
11302        let store = SettingsStore::test(cx);
11303        cx.set_global(store);
11304        theme::init(theme::LoadThemes::JustBase, cx);
11305        release_channel::init("0.0.0", cx);
11306        client::init_settings(cx);
11307        language::init(cx);
11308        Project::init_settings(cx);
11309        workspace::init_settings(cx);
11310        crate::init(cx);
11311    });
11312
11313    update_test_language_settings(cx, f);
11314}
11315
11316pub(crate) fn rust_lang() -> Arc<Language> {
11317    Arc::new(Language::new(
11318        LanguageConfig {
11319            name: "Rust".into(),
11320            matcher: LanguageMatcher {
11321                path_suffixes: vec!["rs".to_string()],
11322                ..Default::default()
11323            },
11324            ..Default::default()
11325        },
11326        Some(tree_sitter_rust::language()),
11327    ))
11328}
11329
11330#[track_caller]
11331fn assert_hunk_revert(
11332    not_reverted_text_with_selections: &str,
11333    expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
11334    expected_reverted_text_with_selections: &str,
11335    base_text: &str,
11336    cx: &mut EditorLspTestContext,
11337) {
11338    cx.set_state(not_reverted_text_with_selections);
11339    cx.update_editor(|editor, cx| {
11340        editor
11341            .buffer()
11342            .read(cx)
11343            .as_singleton()
11344            .unwrap()
11345            .update(cx, |buffer, cx| {
11346                buffer.set_diff_base(Some(base_text.to_string()), cx);
11347            });
11348    });
11349    cx.executor().run_until_parked();
11350
11351    let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
11352        let snapshot = editor
11353            .buffer()
11354            .read(cx)
11355            .as_singleton()
11356            .unwrap()
11357            .read(cx)
11358            .snapshot();
11359        let reverted_hunk_statuses = snapshot
11360            .git_diff_hunks_in_row_range(0..u32::MAX)
11361            .map(|hunk| hunk.status())
11362            .collect::<Vec<_>>();
11363
11364        editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11365        reverted_hunk_statuses
11366    });
11367    cx.executor().run_until_parked();
11368    cx.assert_editor_state(expected_reverted_text_with_selections);
11369    assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
11370}