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