editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    inline_completion_tests::FakeInlineCompletionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseInactiveItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   61};
   62
   63#[gpui::test]
   64fn test_edit_events(cx: &mut TestAppContext) {
   65    init_test(cx, |_| {});
   66
   67    let buffer = cx.new(|cx| {
   68        let mut buffer = language::Buffer::local("123456", cx);
   69        buffer.set_group_interval(Duration::from_secs(1));
   70        buffer
   71    });
   72
   73    let events = Rc::new(RefCell::new(Vec::new()));
   74    let editor1 = cx.add_window({
   75        let events = events.clone();
   76        |window, cx| {
   77            let entity = cx.entity().clone();
   78            cx.subscribe_in(
   79                &entity,
   80                window,
   81                move |_, _, event: &EditorEvent, _, _| match event {
   82                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   83                    EditorEvent::BufferEdited => {
   84                        events.borrow_mut().push(("editor1", "buffer edited"))
   85                    }
   86                    _ => {}
   87                },
   88            )
   89            .detach();
   90            Editor::for_buffer(buffer.clone(), None, window, cx)
   91        }
   92    });
   93
   94    let editor2 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            cx.subscribe_in(
   98                &cx.entity().clone(),
   99                window,
  100                move |_, _, event: &EditorEvent, _, _| match event {
  101                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  102                    EditorEvent::BufferEdited => {
  103                        events.borrow_mut().push(("editor2", "buffer edited"))
  104                    }
  105                    _ => {}
  106                },
  107            )
  108            .detach();
  109            Editor::for_buffer(buffer.clone(), None, window, cx)
  110        }
  111    });
  112
  113    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  114
  115    // Mutating editor 1 will emit an `Edited` event only for that editor.
  116    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  117    assert_eq!(
  118        mem::take(&mut *events.borrow_mut()),
  119        [
  120            ("editor1", "edited"),
  121            ("editor1", "buffer edited"),
  122            ("editor2", "buffer edited"),
  123        ]
  124    );
  125
  126    // Mutating editor 2 will emit an `Edited` event only for that editor.
  127    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  128    assert_eq!(
  129        mem::take(&mut *events.borrow_mut()),
  130        [
  131            ("editor2", "edited"),
  132            ("editor1", "buffer edited"),
  133            ("editor2", "buffer edited"),
  134        ]
  135    );
  136
  137    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  138    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  139    assert_eq!(
  140        mem::take(&mut *events.borrow_mut()),
  141        [
  142            ("editor1", "edited"),
  143            ("editor1", "buffer edited"),
  144            ("editor2", "buffer edited"),
  145        ]
  146    );
  147
  148    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  149    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  150    assert_eq!(
  151        mem::take(&mut *events.borrow_mut()),
  152        [
  153            ("editor1", "edited"),
  154            ("editor1", "buffer edited"),
  155            ("editor2", "buffer edited"),
  156        ]
  157    );
  158
  159    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  160    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  161    assert_eq!(
  162        mem::take(&mut *events.borrow_mut()),
  163        [
  164            ("editor2", "edited"),
  165            ("editor1", "buffer edited"),
  166            ("editor2", "buffer edited"),
  167        ]
  168    );
  169
  170    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  171    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  172    assert_eq!(
  173        mem::take(&mut *events.borrow_mut()),
  174        [
  175            ("editor2", "edited"),
  176            ("editor1", "buffer edited"),
  177            ("editor2", "buffer edited"),
  178        ]
  179    );
  180
  181    // No event is emitted when the mutation is a no-op.
  182    _ = editor2.update(cx, |editor, window, cx| {
  183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  184            s.select_ranges([0..0])
  185        });
  186
  187        editor.backspace(&Backspace, window, cx);
  188    });
  189    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  190}
  191
  192#[gpui::test]
  193fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  194    init_test(cx, |_| {});
  195
  196    let mut now = Instant::now();
  197    let group_interval = Duration::from_millis(1);
  198    let buffer = cx.new(|cx| {
  199        let mut buf = language::Buffer::local("123456", cx);
  200        buf.set_group_interval(group_interval);
  201        buf
  202    });
  203    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  204    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  205
  206    _ = editor.update(cx, |editor, window, cx| {
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  209            s.select_ranges([2..4])
  210        });
  211
  212        editor.insert("cd", window, cx);
  213        editor.end_transaction_at(now, cx);
  214        assert_eq!(editor.text(cx), "12cd56");
  215        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  216
  217        editor.start_transaction_at(now, window, cx);
  218        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  219            s.select_ranges([4..5])
  220        });
  221        editor.insert("e", window, cx);
  222        editor.end_transaction_at(now, cx);
  223        assert_eq!(editor.text(cx), "12cde6");
  224        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  225
  226        now += group_interval + Duration::from_millis(1);
  227        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  228            s.select_ranges([2..2])
  229        });
  230
  231        // Simulate an edit in another editor
  232        buffer.update(cx, |buffer, cx| {
  233            buffer.start_transaction_at(now, cx);
  234            buffer.edit([(0..1, "a")], None, cx);
  235            buffer.edit([(1..1, "b")], None, cx);
  236            buffer.end_transaction_at(now, cx);
  237        });
  238
  239        assert_eq!(editor.text(cx), "ab2cde6");
  240        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  241
  242        // Last transaction happened past the group interval in a different editor.
  243        // Undo it individually and don't restore selections.
  244        editor.undo(&Undo, window, cx);
  245        assert_eq!(editor.text(cx), "12cde6");
  246        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  247
  248        // First two transactions happened within the group interval in this editor.
  249        // Undo them together and restore selections.
  250        editor.undo(&Undo, window, cx);
  251        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  252        assert_eq!(editor.text(cx), "123456");
  253        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  254
  255        // Redo the first two transactions together.
  256        editor.redo(&Redo, window, cx);
  257        assert_eq!(editor.text(cx), "12cde6");
  258        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  259
  260        // Redo the last transaction on its own.
  261        editor.redo(&Redo, window, cx);
  262        assert_eq!(editor.text(cx), "ab2cde6");
  263        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  264
  265        // Test empty transactions.
  266        editor.start_transaction_at(now, window, cx);
  267        editor.end_transaction_at(now, cx);
  268        editor.undo(&Undo, window, cx);
  269        assert_eq!(editor.text(cx), "12cde6");
  270    });
  271}
  272
  273#[gpui::test]
  274fn test_ime_composition(cx: &mut TestAppContext) {
  275    init_test(cx, |_| {});
  276
  277    let buffer = cx.new(|cx| {
  278        let mut buffer = language::Buffer::local("abcde", cx);
  279        // Ensure automatic grouping doesn't occur.
  280        buffer.set_group_interval(Duration::ZERO);
  281        buffer
  282    });
  283
  284    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  285    cx.add_window(|window, cx| {
  286        let mut editor = build_editor(buffer.clone(), window, cx);
  287
  288        // Start a new IME composition.
  289        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  290        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  291        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  292        assert_eq!(editor.text(cx), "äbcde");
  293        assert_eq!(
  294            editor.marked_text_ranges(cx),
  295            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  296        );
  297
  298        // Finalize IME composition.
  299        editor.replace_text_in_range(None, "ā", window, cx);
  300        assert_eq!(editor.text(cx), "ābcde");
  301        assert_eq!(editor.marked_text_ranges(cx), None);
  302
  303        // IME composition edits are grouped and are undone/redone at once.
  304        editor.undo(&Default::default(), window, cx);
  305        assert_eq!(editor.text(cx), "abcde");
  306        assert_eq!(editor.marked_text_ranges(cx), None);
  307        editor.redo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "ābcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310
  311        // Start a new IME composition.
  312        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  313        assert_eq!(
  314            editor.marked_text_ranges(cx),
  315            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  316        );
  317
  318        // Undoing during an IME composition cancels it.
  319        editor.undo(&Default::default(), window, cx);
  320        assert_eq!(editor.text(cx), "ābcde");
  321        assert_eq!(editor.marked_text_ranges(cx), None);
  322
  323        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  324        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  325        assert_eq!(editor.text(cx), "ābcdè");
  326        assert_eq!(
  327            editor.marked_text_ranges(cx),
  328            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  329        );
  330
  331        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  332        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  333        assert_eq!(editor.text(cx), "ābcdę");
  334        assert_eq!(editor.marked_text_ranges(cx), None);
  335
  336        // Start a new IME composition with multiple cursors.
  337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  338            s.select_ranges([
  339                OffsetUtf16(1)..OffsetUtf16(1),
  340                OffsetUtf16(3)..OffsetUtf16(3),
  341                OffsetUtf16(5)..OffsetUtf16(5),
  342            ])
  343        });
  344        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  345        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  346        assert_eq!(
  347            editor.marked_text_ranges(cx),
  348            Some(vec![
  349                OffsetUtf16(0)..OffsetUtf16(3),
  350                OffsetUtf16(4)..OffsetUtf16(7),
  351                OffsetUtf16(8)..OffsetUtf16(11)
  352            ])
  353        );
  354
  355        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  356        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  357        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  358        assert_eq!(
  359            editor.marked_text_ranges(cx),
  360            Some(vec![
  361                OffsetUtf16(1)..OffsetUtf16(2),
  362                OffsetUtf16(5)..OffsetUtf16(6),
  363                OffsetUtf16(9)..OffsetUtf16(10)
  364            ])
  365        );
  366
  367        // Finalize IME composition with multiple cursors.
  368        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  369        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  370        assert_eq!(editor.marked_text_ranges(cx), None);
  371
  372        editor
  373    });
  374}
  375
  376#[gpui::test]
  377fn test_selection_with_mouse(cx: &mut TestAppContext) {
  378    init_test(cx, |_| {});
  379
  380    let editor = cx.add_window(|window, cx| {
  381        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  382        build_editor(buffer, window, cx)
  383    });
  384
  385    _ = editor.update(cx, |editor, window, cx| {
  386        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  387    });
  388    assert_eq!(
  389        editor
  390            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  391            .unwrap(),
  392        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  393    );
  394
  395    _ = editor.update(cx, |editor, window, cx| {
  396        editor.update_selection(
  397            DisplayPoint::new(DisplayRow(3), 3),
  398            0,
  399            gpui::Point::<f32>::default(),
  400            window,
  401            cx,
  402        );
  403    });
  404
  405    assert_eq!(
  406        editor
  407            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  408            .unwrap(),
  409        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  410    );
  411
  412    _ = editor.update(cx, |editor, window, cx| {
  413        editor.update_selection(
  414            DisplayPoint::new(DisplayRow(1), 1),
  415            0,
  416            gpui::Point::<f32>::default(),
  417            window,
  418            cx,
  419        );
  420    });
  421
  422    assert_eq!(
  423        editor
  424            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  425            .unwrap(),
  426        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  427    );
  428
  429    _ = editor.update(cx, |editor, window, cx| {
  430        editor.end_selection(window, cx);
  431        editor.update_selection(
  432            DisplayPoint::new(DisplayRow(3), 3),
  433            0,
  434            gpui::Point::<f32>::default(),
  435            window,
  436            cx,
  437        );
  438    });
  439
  440    assert_eq!(
  441        editor
  442            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  443            .unwrap(),
  444        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  445    );
  446
  447    _ = editor.update(cx, |editor, window, cx| {
  448        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(0), 0),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [
  463            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  464            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  465        ]
  466    );
  467
  468    _ = editor.update(cx, |editor, window, cx| {
  469        editor.end_selection(window, cx);
  470    });
  471
  472    assert_eq!(
  473        editor
  474            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  475            .unwrap(),
  476        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  477    );
  478}
  479
  480#[gpui::test]
  481fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  482    init_test(cx, |_| {});
  483
  484    let editor = cx.add_window(|window, cx| {
  485        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  486        build_editor(buffer, window, cx)
  487    });
  488
  489    _ = editor.update(cx, |editor, window, cx| {
  490        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  491    });
  492
  493    _ = editor.update(cx, |editor, window, cx| {
  494        editor.end_selection(window, cx);
  495    });
  496
  497    _ = editor.update(cx, |editor, window, cx| {
  498        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  499    });
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.end_selection(window, cx);
  503    });
  504
  505    assert_eq!(
  506        editor
  507            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  508            .unwrap(),
  509        [
  510            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  511            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  512        ]
  513    );
  514
  515    _ = editor.update(cx, |editor, window, cx| {
  516        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  517    });
  518
  519    _ = editor.update(cx, |editor, window, cx| {
  520        editor.end_selection(window, cx);
  521    });
  522
  523    assert_eq!(
  524        editor
  525            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  526            .unwrap(),
  527        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  528    );
  529}
  530
  531#[gpui::test]
  532fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  533    init_test(cx, |_| {});
  534
  535    let editor = cx.add_window(|window, cx| {
  536        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  537        build_editor(buffer, window, cx)
  538    });
  539
  540    _ = editor.update(cx, |editor, window, cx| {
  541        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.update_selection(
  550            DisplayPoint::new(DisplayRow(3), 3),
  551            0,
  552            gpui::Point::<f32>::default(),
  553            window,
  554            cx,
  555        );
  556        assert_eq!(
  557            editor.selections.display_ranges(cx),
  558            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  559        );
  560    });
  561
  562    _ = editor.update(cx, |editor, window, cx| {
  563        editor.cancel(&Cancel, window, cx);
  564        editor.update_selection(
  565            DisplayPoint::new(DisplayRow(1), 1),
  566            0,
  567            gpui::Point::<f32>::default(),
  568            window,
  569            cx,
  570        );
  571        assert_eq!(
  572            editor.selections.display_ranges(cx),
  573            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  574        );
  575    });
  576}
  577
  578#[gpui::test]
  579fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  580    init_test(cx, |_| {});
  581
  582    let editor = cx.add_window(|window, cx| {
  583        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  584        build_editor(buffer, window, cx)
  585    });
  586
  587    _ = editor.update(cx, |editor, window, cx| {
  588        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  589        assert_eq!(
  590            editor.selections.display_ranges(cx),
  591            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  592        );
  593
  594        editor.move_down(&Default::default(), window, cx);
  595        assert_eq!(
  596            editor.selections.display_ranges(cx),
  597            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  598        );
  599
  600        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  601        assert_eq!(
  602            editor.selections.display_ranges(cx),
  603            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  604        );
  605
  606        editor.move_up(&Default::default(), window, cx);
  607        assert_eq!(
  608            editor.selections.display_ranges(cx),
  609            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  610        );
  611    });
  612}
  613
  614#[gpui::test]
  615fn test_clone(cx: &mut TestAppContext) {
  616    init_test(cx, |_| {});
  617
  618    let (text, selection_ranges) = marked_text_ranges(
  619        indoc! {"
  620            one
  621            two
  622            threeˇ
  623            four
  624            fiveˇ
  625        "},
  626        true,
  627    );
  628
  629    let editor = cx.add_window(|window, cx| {
  630        let buffer = MultiBuffer::build_simple(&text, cx);
  631        build_editor(buffer, window, cx)
  632    });
  633
  634    _ = editor.update(cx, |editor, window, cx| {
  635        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  636            s.select_ranges(selection_ranges.clone())
  637        });
  638        editor.fold_creases(
  639            vec![
  640                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  641                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  642            ],
  643            true,
  644            window,
  645            cx,
  646        );
  647    });
  648
  649    let cloned_editor = editor
  650        .update(cx, |editor, _, cx| {
  651            cx.open_window(Default::default(), |window, cx| {
  652                cx.new(|cx| editor.clone(window, cx))
  653            })
  654        })
  655        .unwrap()
  656        .unwrap();
  657
  658    let snapshot = editor
  659        .update(cx, |e, window, cx| e.snapshot(window, cx))
  660        .unwrap();
  661    let cloned_snapshot = cloned_editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664
  665    assert_eq!(
  666        cloned_editor
  667            .update(cx, |e, _, cx| e.display_text(cx))
  668            .unwrap(),
  669        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  670    );
  671    assert_eq!(
  672        cloned_snapshot
  673            .folds_in_range(0..text.len())
  674            .collect::<Vec<_>>(),
  675        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  676    );
  677    assert_set_eq!(
  678        cloned_editor
  679            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  680            .unwrap(),
  681        editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  683            .unwrap()
  684    );
  685    assert_set_eq!(
  686        cloned_editor
  687            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  688            .unwrap(),
  689        editor
  690            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  691            .unwrap()
  692    );
  693}
  694
  695#[gpui::test]
  696async fn test_navigation_history(cx: &mut TestAppContext) {
  697    init_test(cx, |_| {});
  698
  699    use workspace::item::Item;
  700
  701    let fs = FakeFs::new(cx.executor());
  702    let project = Project::test(fs, [], cx).await;
  703    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  704    let pane = workspace
  705        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  706        .unwrap();
  707
  708    _ = workspace.update(cx, |_v, window, cx| {
  709        cx.new(|cx| {
  710            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  711            let mut editor = build_editor(buffer.clone(), window, cx);
  712            let handle = cx.entity();
  713            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  714
  715            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  716                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  717            }
  718
  719            // Move the cursor a small distance.
  720            // Nothing is added to the navigation history.
  721            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  724                ])
  725            });
  726            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  727                s.select_display_ranges([
  728                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  729                ])
  730            });
  731            assert!(pop_history(&mut editor, cx).is_none());
  732
  733            // Move the cursor a large distance.
  734            // The history can jump back to the previous position.
  735            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  736                s.select_display_ranges([
  737                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  738                ])
  739            });
  740            let nav_entry = pop_history(&mut editor, cx).unwrap();
  741            editor.navigate(nav_entry.data.unwrap(), window, cx);
  742            assert_eq!(nav_entry.item.id(), cx.entity_id());
  743            assert_eq!(
  744                editor.selections.display_ranges(cx),
  745                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  746            );
  747            assert!(pop_history(&mut editor, cx).is_none());
  748
  749            // Move the cursor a small distance via the mouse.
  750            // Nothing is added to the navigation history.
  751            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  752            editor.end_selection(window, cx);
  753            assert_eq!(
  754                editor.selections.display_ranges(cx),
  755                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  756            );
  757            assert!(pop_history(&mut editor, cx).is_none());
  758
  759            // Move the cursor a large distance via the mouse.
  760            // The history can jump back to the previous position.
  761            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  762            editor.end_selection(window, cx);
  763            assert_eq!(
  764                editor.selections.display_ranges(cx),
  765                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  766            );
  767            let nav_entry = pop_history(&mut editor, cx).unwrap();
  768            editor.navigate(nav_entry.data.unwrap(), window, cx);
  769            assert_eq!(nav_entry.item.id(), cx.entity_id());
  770            assert_eq!(
  771                editor.selections.display_ranges(cx),
  772                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  773            );
  774            assert!(pop_history(&mut editor, cx).is_none());
  775
  776            // Set scroll position to check later
  777            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  778            let original_scroll_position = editor.scroll_manager.anchor();
  779
  780            // Jump to the end of the document and adjust scroll
  781            editor.move_to_end(&MoveToEnd, window, cx);
  782            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  783            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  784
  785            let nav_entry = pop_history(&mut editor, cx).unwrap();
  786            editor.navigate(nav_entry.data.unwrap(), window, cx);
  787            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  788
  789            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  790            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  791            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  792            let invalid_point = Point::new(9999, 0);
  793            editor.navigate(
  794                Box::new(NavigationData {
  795                    cursor_anchor: invalid_anchor,
  796                    cursor_position: invalid_point,
  797                    scroll_anchor: ScrollAnchor {
  798                        anchor: invalid_anchor,
  799                        offset: Default::default(),
  800                    },
  801                    scroll_top_row: invalid_point.row,
  802                }),
  803                window,
  804                cx,
  805            );
  806            assert_eq!(
  807                editor.selections.display_ranges(cx),
  808                &[editor.max_point(cx)..editor.max_point(cx)]
  809            );
  810            assert_eq!(
  811                editor.scroll_position(cx),
  812                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  813            );
  814
  815            editor
  816        })
  817    });
  818}
  819
  820#[gpui::test]
  821fn test_cancel(cx: &mut TestAppContext) {
  822    init_test(cx, |_| {});
  823
  824    let editor = cx.add_window(|window, cx| {
  825        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  826        build_editor(buffer, window, cx)
  827    });
  828
  829    _ = editor.update(cx, |editor, window, cx| {
  830        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  831        editor.update_selection(
  832            DisplayPoint::new(DisplayRow(1), 1),
  833            0,
  834            gpui::Point::<f32>::default(),
  835            window,
  836            cx,
  837        );
  838        editor.end_selection(window, cx);
  839
  840        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  841        editor.update_selection(
  842            DisplayPoint::new(DisplayRow(0), 3),
  843            0,
  844            gpui::Point::<f32>::default(),
  845            window,
  846            cx,
  847        );
  848        editor.end_selection(window, cx);
  849        assert_eq!(
  850            editor.selections.display_ranges(cx),
  851            [
  852                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  853                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  854            ]
  855        );
  856    });
  857
  858    _ = editor.update(cx, |editor, window, cx| {
  859        editor.cancel(&Cancel, window, cx);
  860        assert_eq!(
  861            editor.selections.display_ranges(cx),
  862            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  863        );
  864    });
  865
  866    _ = editor.update(cx, |editor, window, cx| {
  867        editor.cancel(&Cancel, window, cx);
  868        assert_eq!(
  869            editor.selections.display_ranges(cx),
  870            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  871        );
  872    });
  873}
  874
  875#[gpui::test]
  876fn test_fold_action(cx: &mut TestAppContext) {
  877    init_test(cx, |_| {});
  878
  879    let editor = cx.add_window(|window, cx| {
  880        let buffer = MultiBuffer::build_simple(
  881            &"
  882                impl Foo {
  883                    // Hello!
  884
  885                    fn a() {
  886                        1
  887                    }
  888
  889                    fn b() {
  890                        2
  891                    }
  892
  893                    fn c() {
  894                        3
  895                    }
  896                }
  897            "
  898            .unindent(),
  899            cx,
  900        );
  901        build_editor(buffer.clone(), window, cx)
  902    });
  903
  904    _ = editor.update(cx, |editor, window, cx| {
  905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  906            s.select_display_ranges([
  907                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  908            ]);
  909        });
  910        editor.fold(&Fold, window, cx);
  911        assert_eq!(
  912            editor.display_text(cx),
  913            "
  914                impl Foo {
  915                    // Hello!
  916
  917                    fn a() {
  918                        1
  919                    }
  920
  921                    fn b() {⋯
  922                    }
  923
  924                    fn c() {⋯
  925                    }
  926                }
  927            "
  928            .unindent(),
  929        );
  930
  931        editor.fold(&Fold, window, cx);
  932        assert_eq!(
  933            editor.display_text(cx),
  934            "
  935                impl Foo {⋯
  936                }
  937            "
  938            .unindent(),
  939        );
  940
  941        editor.unfold_lines(&UnfoldLines, window, cx);
  942        assert_eq!(
  943            editor.display_text(cx),
  944            "
  945                impl Foo {
  946                    // Hello!
  947
  948                    fn a() {
  949                        1
  950                    }
  951
  952                    fn b() {⋯
  953                    }
  954
  955                    fn c() {⋯
  956                    }
  957                }
  958            "
  959            .unindent(),
  960        );
  961
  962        editor.unfold_lines(&UnfoldLines, window, cx);
  963        assert_eq!(
  964            editor.display_text(cx),
  965            editor.buffer.read(cx).read(cx).text()
  966        );
  967    });
  968}
  969
  970#[gpui::test]
  971fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  972    init_test(cx, |_| {});
  973
  974    let editor = cx.add_window(|window, cx| {
  975        let buffer = MultiBuffer::build_simple(
  976            &"
  977                class Foo:
  978                    # Hello!
  979
  980                    def a():
  981                        print(1)
  982
  983                    def b():
  984                        print(2)
  985
  986                    def c():
  987                        print(3)
  988            "
  989            .unindent(),
  990            cx,
  991        );
  992        build_editor(buffer.clone(), window, cx)
  993    });
  994
  995    _ = editor.update(cx, |editor, window, cx| {
  996        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  997            s.select_display_ranges([
  998                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  999            ]);
 1000        });
 1001        editor.fold(&Fold, window, cx);
 1002        assert_eq!(
 1003            editor.display_text(cx),
 1004            "
 1005                class Foo:
 1006                    # Hello!
 1007
 1008                    def a():
 1009                        print(1)
 1010
 1011                    def b():⋯
 1012
 1013                    def c():⋯
 1014            "
 1015            .unindent(),
 1016        );
 1017
 1018        editor.fold(&Fold, window, cx);
 1019        assert_eq!(
 1020            editor.display_text(cx),
 1021            "
 1022                class Foo:⋯
 1023            "
 1024            .unindent(),
 1025        );
 1026
 1027        editor.unfold_lines(&UnfoldLines, window, cx);
 1028        assert_eq!(
 1029            editor.display_text(cx),
 1030            "
 1031                class Foo:
 1032                    # Hello!
 1033
 1034                    def a():
 1035                        print(1)
 1036
 1037                    def b():⋯
 1038
 1039                    def c():⋯
 1040            "
 1041            .unindent(),
 1042        );
 1043
 1044        editor.unfold_lines(&UnfoldLines, window, cx);
 1045        assert_eq!(
 1046            editor.display_text(cx),
 1047            editor.buffer.read(cx).read(cx).text()
 1048        );
 1049    });
 1050}
 1051
 1052#[gpui::test]
 1053fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1054    init_test(cx, |_| {});
 1055
 1056    let editor = cx.add_window(|window, cx| {
 1057        let buffer = MultiBuffer::build_simple(
 1058            &"
 1059                class Foo:
 1060                    # Hello!
 1061
 1062                    def a():
 1063                        print(1)
 1064
 1065                    def b():
 1066                        print(2)
 1067
 1068
 1069                    def c():
 1070                        print(3)
 1071
 1072
 1073            "
 1074            .unindent(),
 1075            cx,
 1076        );
 1077        build_editor(buffer.clone(), window, cx)
 1078    });
 1079
 1080    _ = editor.update(cx, |editor, window, cx| {
 1081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1082            s.select_display_ranges([
 1083                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1084            ]);
 1085        });
 1086        editor.fold(&Fold, window, cx);
 1087        assert_eq!(
 1088            editor.display_text(cx),
 1089            "
 1090                class Foo:
 1091                    # Hello!
 1092
 1093                    def a():
 1094                        print(1)
 1095
 1096                    def b():⋯
 1097
 1098
 1099                    def c():⋯
 1100
 1101
 1102            "
 1103            .unindent(),
 1104        );
 1105
 1106        editor.fold(&Fold, window, cx);
 1107        assert_eq!(
 1108            editor.display_text(cx),
 1109            "
 1110                class Foo:⋯
 1111
 1112
 1113            "
 1114            .unindent(),
 1115        );
 1116
 1117        editor.unfold_lines(&UnfoldLines, window, cx);
 1118        assert_eq!(
 1119            editor.display_text(cx),
 1120            "
 1121                class Foo:
 1122                    # Hello!
 1123
 1124                    def a():
 1125                        print(1)
 1126
 1127                    def b():⋯
 1128
 1129
 1130                    def c():⋯
 1131
 1132
 1133            "
 1134            .unindent(),
 1135        );
 1136
 1137        editor.unfold_lines(&UnfoldLines, window, cx);
 1138        assert_eq!(
 1139            editor.display_text(cx),
 1140            editor.buffer.read(cx).read(cx).text()
 1141        );
 1142    });
 1143}
 1144
 1145#[gpui::test]
 1146fn test_fold_at_level(cx: &mut TestAppContext) {
 1147    init_test(cx, |_| {});
 1148
 1149    let editor = cx.add_window(|window, cx| {
 1150        let buffer = MultiBuffer::build_simple(
 1151            &"
 1152                class Foo:
 1153                    # Hello!
 1154
 1155                    def a():
 1156                        print(1)
 1157
 1158                    def b():
 1159                        print(2)
 1160
 1161
 1162                class Bar:
 1163                    # World!
 1164
 1165                    def a():
 1166                        print(1)
 1167
 1168                    def b():
 1169                        print(2)
 1170
 1171
 1172            "
 1173            .unindent(),
 1174            cx,
 1175        );
 1176        build_editor(buffer.clone(), window, cx)
 1177    });
 1178
 1179    _ = editor.update(cx, |editor, window, cx| {
 1180        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1181        assert_eq!(
 1182            editor.display_text(cx),
 1183            "
 1184                class Foo:
 1185                    # Hello!
 1186
 1187                    def a():⋯
 1188
 1189                    def b():⋯
 1190
 1191
 1192                class Bar:
 1193                    # World!
 1194
 1195                    def a():⋯
 1196
 1197                    def b():⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1205        assert_eq!(
 1206            editor.display_text(cx),
 1207            "
 1208                class Foo:⋯
 1209
 1210
 1211                class Bar:⋯
 1212
 1213
 1214            "
 1215            .unindent(),
 1216        );
 1217
 1218        editor.unfold_all(&UnfoldAll, window, cx);
 1219        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1220        assert_eq!(
 1221            editor.display_text(cx),
 1222            "
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                class Bar:
 1234                    # World!
 1235
 1236                    def a():
 1237                        print(1)
 1238
 1239                    def b():
 1240                        print(2)
 1241
 1242
 1243            "
 1244            .unindent(),
 1245        );
 1246
 1247        assert_eq!(
 1248            editor.display_text(cx),
 1249            editor.buffer.read(cx).read(cx).text()
 1250        );
 1251    });
 1252}
 1253
 1254#[gpui::test]
 1255fn test_move_cursor(cx: &mut TestAppContext) {
 1256    init_test(cx, |_| {});
 1257
 1258    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1259    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1260
 1261    buffer.update(cx, |buffer, cx| {
 1262        buffer.edit(
 1263            vec![
 1264                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1265                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1266            ],
 1267            None,
 1268            cx,
 1269        );
 1270    });
 1271    _ = editor.update(cx, |editor, window, cx| {
 1272        assert_eq!(
 1273            editor.selections.display_ranges(cx),
 1274            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1275        );
 1276
 1277        editor.move_down(&MoveDown, window, cx);
 1278        assert_eq!(
 1279            editor.selections.display_ranges(cx),
 1280            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1281        );
 1282
 1283        editor.move_right(&MoveRight, window, cx);
 1284        assert_eq!(
 1285            editor.selections.display_ranges(cx),
 1286            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1287        );
 1288
 1289        editor.move_left(&MoveLeft, window, cx);
 1290        assert_eq!(
 1291            editor.selections.display_ranges(cx),
 1292            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1293        );
 1294
 1295        editor.move_up(&MoveUp, window, cx);
 1296        assert_eq!(
 1297            editor.selections.display_ranges(cx),
 1298            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1299        );
 1300
 1301        editor.move_to_end(&MoveToEnd, window, cx);
 1302        assert_eq!(
 1303            editor.selections.display_ranges(cx),
 1304            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1305        );
 1306
 1307        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1308        assert_eq!(
 1309            editor.selections.display_ranges(cx),
 1310            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1311        );
 1312
 1313        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1314            s.select_display_ranges([
 1315                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1316            ]);
 1317        });
 1318        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1319        assert_eq!(
 1320            editor.selections.display_ranges(cx),
 1321            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1322        );
 1323
 1324        editor.select_to_end(&SelectToEnd, window, cx);
 1325        assert_eq!(
 1326            editor.selections.display_ranges(cx),
 1327            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1328        );
 1329    });
 1330}
 1331
 1332#[gpui::test]
 1333fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1334    init_test(cx, |_| {});
 1335
 1336    let editor = cx.add_window(|window, cx| {
 1337        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1338        build_editor(buffer.clone(), window, cx)
 1339    });
 1340
 1341    assert_eq!('🟥'.len_utf8(), 4);
 1342    assert_eq!('α'.len_utf8(), 2);
 1343
 1344    _ = editor.update(cx, |editor, window, cx| {
 1345        editor.fold_creases(
 1346            vec![
 1347                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1348                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1349                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1350            ],
 1351            true,
 1352            window,
 1353            cx,
 1354        );
 1355        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1356
 1357        editor.move_right(&MoveRight, window, cx);
 1358        assert_eq!(
 1359            editor.selections.display_ranges(cx),
 1360            &[empty_range(0, "🟥".len())]
 1361        );
 1362        editor.move_right(&MoveRight, window, cx);
 1363        assert_eq!(
 1364            editor.selections.display_ranges(cx),
 1365            &[empty_range(0, "🟥🟧".len())]
 1366        );
 1367        editor.move_right(&MoveRight, window, cx);
 1368        assert_eq!(
 1369            editor.selections.display_ranges(cx),
 1370            &[empty_range(0, "🟥🟧⋯".len())]
 1371        );
 1372
 1373        editor.move_down(&MoveDown, window, cx);
 1374        assert_eq!(
 1375            editor.selections.display_ranges(cx),
 1376            &[empty_range(1, "ab⋯e".len())]
 1377        );
 1378        editor.move_left(&MoveLeft, window, cx);
 1379        assert_eq!(
 1380            editor.selections.display_ranges(cx),
 1381            &[empty_range(1, "ab⋯".len())]
 1382        );
 1383        editor.move_left(&MoveLeft, window, cx);
 1384        assert_eq!(
 1385            editor.selections.display_ranges(cx),
 1386            &[empty_range(1, "ab".len())]
 1387        );
 1388        editor.move_left(&MoveLeft, window, cx);
 1389        assert_eq!(
 1390            editor.selections.display_ranges(cx),
 1391            &[empty_range(1, "a".len())]
 1392        );
 1393
 1394        editor.move_down(&MoveDown, window, cx);
 1395        assert_eq!(
 1396            editor.selections.display_ranges(cx),
 1397            &[empty_range(2, "α".len())]
 1398        );
 1399        editor.move_right(&MoveRight, window, cx);
 1400        assert_eq!(
 1401            editor.selections.display_ranges(cx),
 1402            &[empty_range(2, "αβ".len())]
 1403        );
 1404        editor.move_right(&MoveRight, window, cx);
 1405        assert_eq!(
 1406            editor.selections.display_ranges(cx),
 1407            &[empty_range(2, "αβ⋯".len())]
 1408        );
 1409        editor.move_right(&MoveRight, window, cx);
 1410        assert_eq!(
 1411            editor.selections.display_ranges(cx),
 1412            &[empty_range(2, "αβ⋯ε".len())]
 1413        );
 1414
 1415        editor.move_up(&MoveUp, window, cx);
 1416        assert_eq!(
 1417            editor.selections.display_ranges(cx),
 1418            &[empty_range(1, "ab⋯e".len())]
 1419        );
 1420        editor.move_down(&MoveDown, window, cx);
 1421        assert_eq!(
 1422            editor.selections.display_ranges(cx),
 1423            &[empty_range(2, "αβ⋯ε".len())]
 1424        );
 1425        editor.move_up(&MoveUp, window, cx);
 1426        assert_eq!(
 1427            editor.selections.display_ranges(cx),
 1428            &[empty_range(1, "ab⋯e".len())]
 1429        );
 1430
 1431        editor.move_up(&MoveUp, window, cx);
 1432        assert_eq!(
 1433            editor.selections.display_ranges(cx),
 1434            &[empty_range(0, "🟥🟧".len())]
 1435        );
 1436        editor.move_left(&MoveLeft, window, cx);
 1437        assert_eq!(
 1438            editor.selections.display_ranges(cx),
 1439            &[empty_range(0, "🟥".len())]
 1440        );
 1441        editor.move_left(&MoveLeft, window, cx);
 1442        assert_eq!(
 1443            editor.selections.display_ranges(cx),
 1444            &[empty_range(0, "".len())]
 1445        );
 1446    });
 1447}
 1448
 1449#[gpui::test]
 1450fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1451    init_test(cx, |_| {});
 1452
 1453    let editor = cx.add_window(|window, cx| {
 1454        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1455        build_editor(buffer.clone(), window, cx)
 1456    });
 1457    _ = editor.update(cx, |editor, window, cx| {
 1458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1459            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1460        });
 1461
 1462        // moving above start of document should move selection to start of document,
 1463        // but the next move down should still be at the original goal_x
 1464        editor.move_up(&MoveUp, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(0, "".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(1, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(2, "αβγ".len())]
 1480        );
 1481
 1482        editor.move_down(&MoveDown, window, cx);
 1483        assert_eq!(
 1484            editor.selections.display_ranges(cx),
 1485            &[empty_range(3, "abcd".len())]
 1486        );
 1487
 1488        editor.move_down(&MoveDown, window, cx);
 1489        assert_eq!(
 1490            editor.selections.display_ranges(cx),
 1491            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1492        );
 1493
 1494        // moving past end of document should not change goal_x
 1495        editor.move_down(&MoveDown, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(5, "".len())]
 1499        );
 1500
 1501        editor.move_down(&MoveDown, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(5, "".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1511        );
 1512
 1513        editor.move_up(&MoveUp, window, cx);
 1514        assert_eq!(
 1515            editor.selections.display_ranges(cx),
 1516            &[empty_range(3, "abcd".len())]
 1517        );
 1518
 1519        editor.move_up(&MoveUp, window, cx);
 1520        assert_eq!(
 1521            editor.selections.display_ranges(cx),
 1522            &[empty_range(2, "αβγ".len())]
 1523        );
 1524    });
 1525}
 1526
 1527#[gpui::test]
 1528fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1529    init_test(cx, |_| {});
 1530    let move_to_beg = MoveToBeginningOfLine {
 1531        stop_at_soft_wraps: true,
 1532        stop_at_indent: true,
 1533    };
 1534
 1535    let delete_to_beg = DeleteToBeginningOfLine {
 1536        stop_at_indent: false,
 1537    };
 1538
 1539    let move_to_end = MoveToEndOfLine {
 1540        stop_at_soft_wraps: true,
 1541    };
 1542
 1543    let editor = cx.add_window(|window, cx| {
 1544        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1545        build_editor(buffer, window, cx)
 1546    });
 1547    _ = editor.update(cx, |editor, window, cx| {
 1548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1549            s.select_display_ranges([
 1550                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1551                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1552            ]);
 1553        });
 1554    });
 1555
 1556    _ = editor.update(cx, |editor, window, cx| {
 1557        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1558        assert_eq!(
 1559            editor.selections.display_ranges(cx),
 1560            &[
 1561                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1563            ]
 1564        );
 1565    });
 1566
 1567    _ = editor.update(cx, |editor, window, cx| {
 1568        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1569        assert_eq!(
 1570            editor.selections.display_ranges(cx),
 1571            &[
 1572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1573                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1574            ]
 1575        );
 1576    });
 1577
 1578    _ = editor.update(cx, |editor, window, cx| {
 1579        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1580        assert_eq!(
 1581            editor.selections.display_ranges(cx),
 1582            &[
 1583                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1584                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1585            ]
 1586        );
 1587    });
 1588
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    // Moving to the end of line again is a no-op.
 1601    _ = editor.update(cx, |editor, window, cx| {
 1602        editor.move_to_end_of_line(&move_to_end, window, cx);
 1603        assert_eq!(
 1604            editor.selections.display_ranges(cx),
 1605            &[
 1606                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1607                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1608            ]
 1609        );
 1610    });
 1611
 1612    _ = editor.update(cx, |editor, window, cx| {
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        editor.select_to_beginning_of_line(
 1615            &SelectToBeginningOfLine {
 1616                stop_at_soft_wraps: true,
 1617                stop_at_indent: true,
 1618            },
 1619            window,
 1620            cx,
 1621        );
 1622        assert_eq!(
 1623            editor.selections.display_ranges(cx),
 1624            &[
 1625                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1626                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1627            ]
 1628        );
 1629    });
 1630
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.select_to_beginning_of_line(
 1633            &SelectToBeginningOfLine {
 1634                stop_at_soft_wraps: true,
 1635                stop_at_indent: true,
 1636            },
 1637            window,
 1638            cx,
 1639        );
 1640        assert_eq!(
 1641            editor.selections.display_ranges(cx),
 1642            &[
 1643                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1644                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1645            ]
 1646        );
 1647    });
 1648
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.select_to_beginning_of_line(
 1651            &SelectToBeginningOfLine {
 1652                stop_at_soft_wraps: true,
 1653                stop_at_indent: true,
 1654            },
 1655            window,
 1656            cx,
 1657        );
 1658        assert_eq!(
 1659            editor.selections.display_ranges(cx),
 1660            &[
 1661                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1662                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1663            ]
 1664        );
 1665    });
 1666
 1667    _ = editor.update(cx, |editor, window, cx| {
 1668        editor.select_to_end_of_line(
 1669            &SelectToEndOfLine {
 1670                stop_at_soft_wraps: true,
 1671            },
 1672            window,
 1673            cx,
 1674        );
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1686        assert_eq!(editor.display_text(cx), "ab\n  de");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1691                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1692            ]
 1693        );
 1694    });
 1695
 1696    _ = editor.update(cx, |editor, window, cx| {
 1697        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1698        assert_eq!(editor.display_text(cx), "\n");
 1699        assert_eq!(
 1700            editor.selections.display_ranges(cx),
 1701            &[
 1702                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1703                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1704            ]
 1705        );
 1706    });
 1707}
 1708
 1709#[gpui::test]
 1710fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1711    init_test(cx, |_| {});
 1712    let move_to_beg = MoveToBeginningOfLine {
 1713        stop_at_soft_wraps: false,
 1714        stop_at_indent: false,
 1715    };
 1716
 1717    let move_to_end = MoveToEndOfLine {
 1718        stop_at_soft_wraps: false,
 1719    };
 1720
 1721    let editor = cx.add_window(|window, cx| {
 1722        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1723        build_editor(buffer, window, cx)
 1724    });
 1725
 1726    _ = editor.update(cx, |editor, window, cx| {
 1727        editor.set_wrap_width(Some(140.0.into()), cx);
 1728
 1729        // We expect the following lines after wrapping
 1730        // ```
 1731        // thequickbrownfox
 1732        // jumpedoverthelazydo
 1733        // gs
 1734        // ```
 1735        // The final `gs` was soft-wrapped onto a new line.
 1736        assert_eq!(
 1737            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1738            editor.display_text(cx),
 1739        );
 1740
 1741        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1742        // Start the cursor at the `k` on the first line
 1743        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1744            s.select_display_ranges([
 1745                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1746            ]);
 1747        });
 1748
 1749        // Moving to the beginning of the line should put us at the beginning of the line.
 1750        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1751        assert_eq!(
 1752            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1753            editor.selections.display_ranges(cx)
 1754        );
 1755
 1756        // Moving to the end of the line should put us at the end of the line.
 1757        editor.move_to_end_of_line(&move_to_end, window, cx);
 1758        assert_eq!(
 1759            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1760            editor.selections.display_ranges(cx)
 1761        );
 1762
 1763        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1764        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1766            s.select_display_ranges([
 1767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1768            ]);
 1769        });
 1770
 1771        // Moving to the beginning of the line should put us at the start of the second line of
 1772        // display text, i.e., the `j`.
 1773        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1774        assert_eq!(
 1775            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1776            editor.selections.display_ranges(cx)
 1777        );
 1778
 1779        // Moving to the beginning of the line again should be a no-op.
 1780        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1781        assert_eq!(
 1782            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1783            editor.selections.display_ranges(cx)
 1784        );
 1785
 1786        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1787        // next display line.
 1788        editor.move_to_end_of_line(&move_to_end, window, cx);
 1789        assert_eq!(
 1790            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1791            editor.selections.display_ranges(cx)
 1792        );
 1793
 1794        // Moving to the end of the line again should be a no-op.
 1795        editor.move_to_end_of_line(&move_to_end, window, cx);
 1796        assert_eq!(
 1797            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1798            editor.selections.display_ranges(cx)
 1799        );
 1800    });
 1801}
 1802
 1803#[gpui::test]
 1804fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1805    init_test(cx, |_| {});
 1806
 1807    let move_to_beg = MoveToBeginningOfLine {
 1808        stop_at_soft_wraps: true,
 1809        stop_at_indent: true,
 1810    };
 1811
 1812    let select_to_beg = SelectToBeginningOfLine {
 1813        stop_at_soft_wraps: true,
 1814        stop_at_indent: true,
 1815    };
 1816
 1817    let delete_to_beg = DeleteToBeginningOfLine {
 1818        stop_at_indent: true,
 1819    };
 1820
 1821    let move_to_end = MoveToEndOfLine {
 1822        stop_at_soft_wraps: false,
 1823    };
 1824
 1825    let editor = cx.add_window(|window, cx| {
 1826        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1827        build_editor(buffer, window, cx)
 1828    });
 1829
 1830    _ = editor.update(cx, |editor, window, cx| {
 1831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1832            s.select_display_ranges([
 1833                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1834                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1835            ]);
 1836        });
 1837
 1838        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1839        // and the second cursor at the first non-whitespace character in the line.
 1840        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1841        assert_eq!(
 1842            editor.selections.display_ranges(cx),
 1843            &[
 1844                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1845                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1846            ]
 1847        );
 1848
 1849        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1850        // and should move the second cursor to the beginning of the line.
 1851        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1852        assert_eq!(
 1853            editor.selections.display_ranges(cx),
 1854            &[
 1855                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1856                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1857            ]
 1858        );
 1859
 1860        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1861        // and should move the second cursor back to the first non-whitespace character in the line.
 1862        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1863        assert_eq!(
 1864            editor.selections.display_ranges(cx),
 1865            &[
 1866                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1867                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1868            ]
 1869        );
 1870
 1871        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1872        // and to the first non-whitespace character in the line for the second cursor.
 1873        editor.move_to_end_of_line(&move_to_end, window, cx);
 1874        editor.move_left(&MoveLeft, window, cx);
 1875        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1876        assert_eq!(
 1877            editor.selections.display_ranges(cx),
 1878            &[
 1879                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1880                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1881            ]
 1882        );
 1883
 1884        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1885        // and should select to the beginning of the line for the second cursor.
 1886        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1887        assert_eq!(
 1888            editor.selections.display_ranges(cx),
 1889            &[
 1890                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1891                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1892            ]
 1893        );
 1894
 1895        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1896        // and should delete to the first non-whitespace character in the line for the second cursor.
 1897        editor.move_to_end_of_line(&move_to_end, window, cx);
 1898        editor.move_left(&MoveLeft, window, cx);
 1899        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1900        assert_eq!(editor.text(cx), "c\n  f");
 1901    });
 1902}
 1903
 1904#[gpui::test]
 1905fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let editor = cx.add_window(|window, cx| {
 1909        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1910        build_editor(buffer, window, cx)
 1911    });
 1912    _ = editor.update(cx, |editor, window, cx| {
 1913        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1914            s.select_display_ranges([
 1915                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1916                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1917            ])
 1918        });
 1919        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1920        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1921
 1922        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1923        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1924
 1925        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1926        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1927
 1928        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1929        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1930
 1931        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1932        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1933
 1934        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1935        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1936
 1937        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1938        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1939
 1940        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1941        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1942
 1943        editor.move_right(&MoveRight, window, cx);
 1944        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1945        assert_selection_ranges(
 1946            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1947            editor,
 1948            cx,
 1949        );
 1950
 1951        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1952        assert_selection_ranges(
 1953            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1954            editor,
 1955            cx,
 1956        );
 1957
 1958        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1959        assert_selection_ranges(
 1960            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 1961            editor,
 1962            cx,
 1963        );
 1964    });
 1965}
 1966
 1967#[gpui::test]
 1968fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1969    init_test(cx, |_| {});
 1970
 1971    let editor = cx.add_window(|window, cx| {
 1972        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1973        build_editor(buffer, window, cx)
 1974    });
 1975
 1976    _ = editor.update(cx, |editor, window, cx| {
 1977        editor.set_wrap_width(Some(140.0.into()), cx);
 1978        assert_eq!(
 1979            editor.display_text(cx),
 1980            "use one::{\n    two::three::\n    four::five\n};"
 1981        );
 1982
 1983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1984            s.select_display_ranges([
 1985                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1986            ]);
 1987        });
 1988
 1989        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1990        assert_eq!(
 1991            editor.selections.display_ranges(cx),
 1992            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1993        );
 1994
 1995        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1996        assert_eq!(
 1997            editor.selections.display_ranges(cx),
 1998            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1999        );
 2000
 2001        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2002        assert_eq!(
 2003            editor.selections.display_ranges(cx),
 2004            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2005        );
 2006
 2007        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2008        assert_eq!(
 2009            editor.selections.display_ranges(cx),
 2010            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2011        );
 2012
 2013        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2014        assert_eq!(
 2015            editor.selections.display_ranges(cx),
 2016            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2017        );
 2018
 2019        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2020        assert_eq!(
 2021            editor.selections.display_ranges(cx),
 2022            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2023        );
 2024    });
 2025}
 2026
 2027#[gpui::test]
 2028async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2029    init_test(cx, |_| {});
 2030    let mut cx = EditorTestContext::new(cx).await;
 2031
 2032    let line_height = cx.editor(|editor, window, _| {
 2033        editor
 2034            .style()
 2035            .unwrap()
 2036            .text
 2037            .line_height_in_pixels(window.rem_size())
 2038    });
 2039    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2040
 2041    cx.set_state(
 2042        &r#"ˇone
 2043        two
 2044
 2045        three
 2046        fourˇ
 2047        five
 2048
 2049        six"#
 2050            .unindent(),
 2051    );
 2052
 2053    cx.update_editor(|editor, window, cx| {
 2054        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2055    });
 2056    cx.assert_editor_state(
 2057        &r#"one
 2058        two
 2059        ˇ
 2060        three
 2061        four
 2062        five
 2063        ˇ
 2064        six"#
 2065            .unindent(),
 2066    );
 2067
 2068    cx.update_editor(|editor, window, cx| {
 2069        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2070    });
 2071    cx.assert_editor_state(
 2072        &r#"one
 2073        two
 2074
 2075        three
 2076        four
 2077        five
 2078        ˇ
 2079        sixˇ"#
 2080            .unindent(),
 2081    );
 2082
 2083    cx.update_editor(|editor, window, cx| {
 2084        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2085    });
 2086    cx.assert_editor_state(
 2087        &r#"one
 2088        two
 2089
 2090        three
 2091        four
 2092        five
 2093
 2094        sixˇ"#
 2095            .unindent(),
 2096    );
 2097
 2098    cx.update_editor(|editor, window, cx| {
 2099        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2100    });
 2101    cx.assert_editor_state(
 2102        &r#"one
 2103        two
 2104
 2105        three
 2106        four
 2107        five
 2108        ˇ
 2109        six"#
 2110            .unindent(),
 2111    );
 2112
 2113    cx.update_editor(|editor, window, cx| {
 2114        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2115    });
 2116    cx.assert_editor_state(
 2117        &r#"one
 2118        two
 2119        ˇ
 2120        three
 2121        four
 2122        five
 2123
 2124        six"#
 2125            .unindent(),
 2126    );
 2127
 2128    cx.update_editor(|editor, window, cx| {
 2129        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2130    });
 2131    cx.assert_editor_state(
 2132        &r#"ˇone
 2133        two
 2134
 2135        three
 2136        four
 2137        five
 2138
 2139        six"#
 2140            .unindent(),
 2141    );
 2142}
 2143
 2144#[gpui::test]
 2145async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2146    init_test(cx, |_| {});
 2147    let mut cx = EditorTestContext::new(cx).await;
 2148    let line_height = cx.editor(|editor, window, _| {
 2149        editor
 2150            .style()
 2151            .unwrap()
 2152            .text
 2153            .line_height_in_pixels(window.rem_size())
 2154    });
 2155    let window = cx.window;
 2156    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2157
 2158    cx.set_state(
 2159        r#"ˇone
 2160        two
 2161        three
 2162        four
 2163        five
 2164        six
 2165        seven
 2166        eight
 2167        nine
 2168        ten
 2169        "#,
 2170    );
 2171
 2172    cx.update_editor(|editor, window, cx| {
 2173        assert_eq!(
 2174            editor.snapshot(window, cx).scroll_position(),
 2175            gpui::Point::new(0., 0.)
 2176        );
 2177        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2178        assert_eq!(
 2179            editor.snapshot(window, cx).scroll_position(),
 2180            gpui::Point::new(0., 3.)
 2181        );
 2182        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 6.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192
 2193        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2194        assert_eq!(
 2195            editor.snapshot(window, cx).scroll_position(),
 2196            gpui::Point::new(0., 1.)
 2197        );
 2198        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2199        assert_eq!(
 2200            editor.snapshot(window, cx).scroll_position(),
 2201            gpui::Point::new(0., 3.)
 2202        );
 2203    });
 2204}
 2205
 2206#[gpui::test]
 2207async fn test_autoscroll(cx: &mut TestAppContext) {
 2208    init_test(cx, |_| {});
 2209    let mut cx = EditorTestContext::new(cx).await;
 2210
 2211    let line_height = cx.update_editor(|editor, window, cx| {
 2212        editor.set_vertical_scroll_margin(2, cx);
 2213        editor
 2214            .style()
 2215            .unwrap()
 2216            .text
 2217            .line_height_in_pixels(window.rem_size())
 2218    });
 2219    let window = cx.window;
 2220    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2221
 2222    cx.set_state(
 2223        r#"ˇone
 2224            two
 2225            three
 2226            four
 2227            five
 2228            six
 2229            seven
 2230            eight
 2231            nine
 2232            ten
 2233        "#,
 2234    );
 2235    cx.update_editor(|editor, window, cx| {
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 0.0)
 2239        );
 2240    });
 2241
 2242    // Add a cursor below the visible area. Since both cursors cannot fit
 2243    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2244    // allows the vertical scroll margin below that cursor.
 2245    cx.update_editor(|editor, window, cx| {
 2246        editor.change_selections(Default::default(), window, cx, |selections| {
 2247            selections.select_ranges([
 2248                Point::new(0, 0)..Point::new(0, 0),
 2249                Point::new(6, 0)..Point::new(6, 0),
 2250            ]);
 2251        })
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 3.0)
 2257        );
 2258    });
 2259
 2260    // Move down. The editor cursor scrolls down to track the newest cursor.
 2261    cx.update_editor(|editor, window, cx| {
 2262        editor.move_down(&Default::default(), window, cx);
 2263    });
 2264    cx.update_editor(|editor, window, cx| {
 2265        assert_eq!(
 2266            editor.snapshot(window, cx).scroll_position(),
 2267            gpui::Point::new(0., 4.0)
 2268        );
 2269    });
 2270
 2271    // Add a cursor above the visible area. Since both cursors fit on screen,
 2272    // the editor scrolls to show both.
 2273    cx.update_editor(|editor, window, cx| {
 2274        editor.change_selections(Default::default(), window, cx, |selections| {
 2275            selections.select_ranges([
 2276                Point::new(1, 0)..Point::new(1, 0),
 2277                Point::new(6, 0)..Point::new(6, 0),
 2278            ]);
 2279        })
 2280    });
 2281    cx.update_editor(|editor, window, cx| {
 2282        assert_eq!(
 2283            editor.snapshot(window, cx).scroll_position(),
 2284            gpui::Point::new(0., 1.0)
 2285        );
 2286    });
 2287}
 2288
 2289#[gpui::test]
 2290async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2291    init_test(cx, |_| {});
 2292    let mut cx = EditorTestContext::new(cx).await;
 2293
 2294    let line_height = cx.editor(|editor, window, _cx| {
 2295        editor
 2296            .style()
 2297            .unwrap()
 2298            .text
 2299            .line_height_in_pixels(window.rem_size())
 2300    });
 2301    let window = cx.window;
 2302    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2303    cx.set_state(
 2304        &r#"
 2305        ˇone
 2306        two
 2307        threeˇ
 2308        four
 2309        five
 2310        six
 2311        seven
 2312        eight
 2313        nine
 2314        ten
 2315        "#
 2316        .unindent(),
 2317    );
 2318
 2319    cx.update_editor(|editor, window, cx| {
 2320        editor.move_page_down(&MovePageDown::default(), window, cx)
 2321    });
 2322    cx.assert_editor_state(
 2323        &r#"
 2324        one
 2325        two
 2326        three
 2327        ˇfour
 2328        five
 2329        sixˇ
 2330        seven
 2331        eight
 2332        nine
 2333        ten
 2334        "#
 2335        .unindent(),
 2336    );
 2337
 2338    cx.update_editor(|editor, window, cx| {
 2339        editor.move_page_down(&MovePageDown::default(), window, cx)
 2340    });
 2341    cx.assert_editor_state(
 2342        &r#"
 2343        one
 2344        two
 2345        three
 2346        four
 2347        five
 2348        six
 2349        ˇseven
 2350        eight
 2351        nineˇ
 2352        ten
 2353        "#
 2354        .unindent(),
 2355    );
 2356
 2357    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2358    cx.assert_editor_state(
 2359        &r#"
 2360        one
 2361        two
 2362        three
 2363        ˇfour
 2364        five
 2365        sixˇ
 2366        seven
 2367        eight
 2368        nine
 2369        ten
 2370        "#
 2371        .unindent(),
 2372    );
 2373
 2374    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2375    cx.assert_editor_state(
 2376        &r#"
 2377        ˇone
 2378        two
 2379        threeˇ
 2380        four
 2381        five
 2382        six
 2383        seven
 2384        eight
 2385        nine
 2386        ten
 2387        "#
 2388        .unindent(),
 2389    );
 2390
 2391    // Test select collapsing
 2392    cx.update_editor(|editor, window, cx| {
 2393        editor.move_page_down(&MovePageDown::default(), window, cx);
 2394        editor.move_page_down(&MovePageDown::default(), window, cx);
 2395        editor.move_page_down(&MovePageDown::default(), window, cx);
 2396    });
 2397    cx.assert_editor_state(
 2398        &r#"
 2399        one
 2400        two
 2401        three
 2402        four
 2403        five
 2404        six
 2405        seven
 2406        eight
 2407        nine
 2408        ˇten
 2409        ˇ"#
 2410        .unindent(),
 2411    );
 2412}
 2413
 2414#[gpui::test]
 2415async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2416    init_test(cx, |_| {});
 2417    let mut cx = EditorTestContext::new(cx).await;
 2418    cx.set_state("one «two threeˇ» four");
 2419    cx.update_editor(|editor, window, cx| {
 2420        editor.delete_to_beginning_of_line(
 2421            &DeleteToBeginningOfLine {
 2422                stop_at_indent: false,
 2423            },
 2424            window,
 2425            cx,
 2426        );
 2427        assert_eq!(editor.text(cx), " four");
 2428    });
 2429}
 2430
 2431#[gpui::test]
 2432fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2433    init_test(cx, |_| {});
 2434
 2435    let editor = cx.add_window(|window, cx| {
 2436        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2437        build_editor(buffer.clone(), window, cx)
 2438    });
 2439
 2440    _ = editor.update(cx, |editor, window, cx| {
 2441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2442            s.select_display_ranges([
 2443                // an empty selection - the preceding word fragment is deleted
 2444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2445                // characters selected - they are deleted
 2446                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2447            ])
 2448        });
 2449        editor.delete_to_previous_word_start(
 2450            &DeleteToPreviousWordStart {
 2451                ignore_newlines: false,
 2452            },
 2453            window,
 2454            cx,
 2455        );
 2456        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2457    });
 2458
 2459    _ = editor.update(cx, |editor, window, cx| {
 2460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2461            s.select_display_ranges([
 2462                // an empty selection - the following word fragment is deleted
 2463                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2464                // characters selected - they are deleted
 2465                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2466            ])
 2467        });
 2468        editor.delete_to_next_word_end(
 2469            &DeleteToNextWordEnd {
 2470                ignore_newlines: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let editor = cx.add_window(|window, cx| {
 2484        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2485        build_editor(buffer.clone(), window, cx)
 2486    });
 2487    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2488        ignore_newlines: false,
 2489    };
 2490    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2491        ignore_newlines: true,
 2492    };
 2493
 2494    _ = editor.update(cx, |editor, window, cx| {
 2495        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2496            s.select_display_ranges([
 2497                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2498            ])
 2499        });
 2500        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2502        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2504        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2505        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2506        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2507        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2508        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2509        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2510        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2511        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2512    });
 2513}
 2514
 2515#[gpui::test]
 2516fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2517    init_test(cx, |_| {});
 2518
 2519    let editor = cx.add_window(|window, cx| {
 2520        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2521        build_editor(buffer.clone(), window, cx)
 2522    });
 2523    let del_to_next_word_end = DeleteToNextWordEnd {
 2524        ignore_newlines: false,
 2525    };
 2526    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2527        ignore_newlines: true,
 2528    };
 2529
 2530    _ = editor.update(cx, |editor, window, cx| {
 2531        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2532            s.select_display_ranges([
 2533                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2534            ])
 2535        });
 2536        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2537        assert_eq!(
 2538            editor.buffer.read(cx).read(cx).text(),
 2539            "one\n   two\nthree\n   four"
 2540        );
 2541        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2542        assert_eq!(
 2543            editor.buffer.read(cx).read(cx).text(),
 2544            "\n   two\nthree\n   four"
 2545        );
 2546        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2547        assert_eq!(
 2548            editor.buffer.read(cx).read(cx).text(),
 2549            "two\nthree\n   four"
 2550        );
 2551        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2553        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2555        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2566        build_editor(buffer.clone(), window, cx)
 2567    });
 2568
 2569    _ = editor.update(cx, |editor, window, cx| {
 2570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2571            s.select_display_ranges([
 2572                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2573                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2574                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2575            ])
 2576        });
 2577
 2578        editor.newline(&Newline, window, cx);
 2579        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2580    });
 2581}
 2582
 2583#[gpui::test]
 2584fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2585    init_test(cx, |_| {});
 2586
 2587    let editor = cx.add_window(|window, cx| {
 2588        let buffer = MultiBuffer::build_simple(
 2589            "
 2590                a
 2591                b(
 2592                    X
 2593                )
 2594                c(
 2595                    X
 2596                )
 2597            "
 2598            .unindent()
 2599            .as_str(),
 2600            cx,
 2601        );
 2602        let mut editor = build_editor(buffer.clone(), window, cx);
 2603        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2604            s.select_ranges([
 2605                Point::new(2, 4)..Point::new(2, 5),
 2606                Point::new(5, 4)..Point::new(5, 5),
 2607            ])
 2608        });
 2609        editor
 2610    });
 2611
 2612    _ = editor.update(cx, |editor, window, cx| {
 2613        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2614        editor.buffer.update(cx, |buffer, cx| {
 2615            buffer.edit(
 2616                [
 2617                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2618                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2619                ],
 2620                None,
 2621                cx,
 2622            );
 2623            assert_eq!(
 2624                buffer.read(cx).text(),
 2625                "
 2626                    a
 2627                    b()
 2628                    c()
 2629                "
 2630                .unindent()
 2631            );
 2632        });
 2633        assert_eq!(
 2634            editor.selections.ranges(cx),
 2635            &[
 2636                Point::new(1, 2)..Point::new(1, 2),
 2637                Point::new(2, 2)..Point::new(2, 2),
 2638            ],
 2639        );
 2640
 2641        editor.newline(&Newline, window, cx);
 2642        assert_eq!(
 2643            editor.text(cx),
 2644            "
 2645                a
 2646                b(
 2647                )
 2648                c(
 2649                )
 2650            "
 2651            .unindent()
 2652        );
 2653
 2654        // The selections are moved after the inserted newlines
 2655        assert_eq!(
 2656            editor.selections.ranges(cx),
 2657            &[
 2658                Point::new(2, 0)..Point::new(2, 0),
 2659                Point::new(4, 0)..Point::new(4, 0),
 2660            ],
 2661        );
 2662    });
 2663}
 2664
 2665#[gpui::test]
 2666async fn test_newline_above(cx: &mut TestAppContext) {
 2667    init_test(cx, |settings| {
 2668        settings.defaults.tab_size = NonZeroU32::new(4)
 2669    });
 2670
 2671    let language = Arc::new(
 2672        Language::new(
 2673            LanguageConfig::default(),
 2674            Some(tree_sitter_rust::LANGUAGE.into()),
 2675        )
 2676        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2677        .unwrap(),
 2678    );
 2679
 2680    let mut cx = EditorTestContext::new(cx).await;
 2681    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2682    cx.set_state(indoc! {"
 2683        const a: ˇA = (
 2684 2685                «const_functionˇ»(ˇ),
 2686                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2687 2688        ˇ);ˇ
 2689    "});
 2690
 2691    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2692    cx.assert_editor_state(indoc! {"
 2693        ˇ
 2694        const a: A = (
 2695            ˇ
 2696            (
 2697                ˇ
 2698                ˇ
 2699                const_function(),
 2700                ˇ
 2701                ˇ
 2702                ˇ
 2703                ˇ
 2704                something_else,
 2705                ˇ
 2706            )
 2707            ˇ
 2708            ˇ
 2709        );
 2710    "});
 2711}
 2712
 2713#[gpui::test]
 2714async fn test_newline_below(cx: &mut TestAppContext) {
 2715    init_test(cx, |settings| {
 2716        settings.defaults.tab_size = NonZeroU32::new(4)
 2717    });
 2718
 2719    let language = Arc::new(
 2720        Language::new(
 2721            LanguageConfig::default(),
 2722            Some(tree_sitter_rust::LANGUAGE.into()),
 2723        )
 2724        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2725        .unwrap(),
 2726    );
 2727
 2728    let mut cx = EditorTestContext::new(cx).await;
 2729    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2730    cx.set_state(indoc! {"
 2731        const a: ˇA = (
 2732 2733                «const_functionˇ»(ˇ),
 2734                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2735 2736        ˇ);ˇ
 2737    "});
 2738
 2739    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2740    cx.assert_editor_state(indoc! {"
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                const_function(),
 2746                ˇ
 2747                ˇ
 2748                something_else,
 2749                ˇ
 2750                ˇ
 2751                ˇ
 2752                ˇ
 2753            )
 2754            ˇ
 2755        );
 2756        ˇ
 2757        ˇ
 2758    "});
 2759}
 2760
 2761#[gpui::test]
 2762async fn test_newline_comments(cx: &mut TestAppContext) {
 2763    init_test(cx, |settings| {
 2764        settings.defaults.tab_size = NonZeroU32::new(4)
 2765    });
 2766
 2767    let language = Arc::new(Language::new(
 2768        LanguageConfig {
 2769            line_comments: vec!["// ".into()],
 2770            ..LanguageConfig::default()
 2771        },
 2772        None,
 2773    ));
 2774    {
 2775        let mut cx = EditorTestContext::new(cx).await;
 2776        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777        cx.set_state(indoc! {"
 2778        // Fooˇ
 2779    "});
 2780
 2781        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2782        cx.assert_editor_state(indoc! {"
 2783        // Foo
 2784        // ˇ
 2785    "});
 2786        // Ensure that we add comment prefix when existing line contains space
 2787        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2788        cx.assert_editor_state(
 2789            indoc! {"
 2790        // Foo
 2791        //s
 2792        // ˇ
 2793    "}
 2794            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2795            .as_str(),
 2796        );
 2797        // Ensure that we add comment prefix when existing line does not contain space
 2798        cx.set_state(indoc! {"
 2799        // Foo
 2800        //ˇ
 2801    "});
 2802        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2803        cx.assert_editor_state(indoc! {"
 2804        // Foo
 2805        //
 2806        // ˇ
 2807    "});
 2808        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2809        cx.set_state(indoc! {"
 2810        ˇ// Foo
 2811    "});
 2812        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2813        cx.assert_editor_state(indoc! {"
 2814
 2815        ˇ// Foo
 2816    "});
 2817    }
 2818    // Ensure that comment continuations can be disabled.
 2819    update_test_language_settings(cx, |settings| {
 2820        settings.defaults.extend_comment_on_newline = Some(false);
 2821    });
 2822    let mut cx = EditorTestContext::new(cx).await;
 2823    cx.set_state(indoc! {"
 2824        // Fooˇ
 2825    "});
 2826    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827    cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        ˇ
 2830    "});
 2831}
 2832
 2833#[gpui::test]
 2834async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2835    init_test(cx, |settings| {
 2836        settings.defaults.tab_size = NonZeroU32::new(4)
 2837    });
 2838
 2839    let language = Arc::new(Language::new(
 2840        LanguageConfig {
 2841            line_comments: vec!["// ".into(), "/// ".into()],
 2842            ..LanguageConfig::default()
 2843        },
 2844        None,
 2845    ));
 2846    {
 2847        let mut cx = EditorTestContext::new(cx).await;
 2848        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2849        cx.set_state(indoc! {"
 2850        //ˇ
 2851    "});
 2852        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2853        cx.assert_editor_state(indoc! {"
 2854        //
 2855        // ˇ
 2856    "});
 2857
 2858        cx.set_state(indoc! {"
 2859        ///ˇ
 2860    "});
 2861        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2862        cx.assert_editor_state(indoc! {"
 2863        ///
 2864        /// ˇ
 2865    "});
 2866    }
 2867}
 2868
 2869#[gpui::test]
 2870async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2871    init_test(cx, |settings| {
 2872        settings.defaults.tab_size = NonZeroU32::new(4)
 2873    });
 2874
 2875    let language = Arc::new(
 2876        Language::new(
 2877            LanguageConfig {
 2878                documentation: Some(language::DocumentationConfig {
 2879                    start: "/**".into(),
 2880                    end: "*/".into(),
 2881                    prefix: "* ".into(),
 2882                    tab_size: NonZeroU32::new(1).unwrap(),
 2883                }),
 2884
 2885                ..LanguageConfig::default()
 2886            },
 2887            Some(tree_sitter_rust::LANGUAGE.into()),
 2888        )
 2889        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2890        .unwrap(),
 2891    );
 2892
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        /**ˇ
 2898    "});
 2899
 2900        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2901        cx.assert_editor_state(indoc! {"
 2902        /**
 2903         * ˇ
 2904    "});
 2905        // Ensure that if cursor is before the comment start,
 2906        // we do not actually insert a comment prefix.
 2907        cx.set_state(indoc! {"
 2908        ˇ/**
 2909    "});
 2910        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2911        cx.assert_editor_state(indoc! {"
 2912
 2913        ˇ/**
 2914    "});
 2915        // Ensure that if cursor is between it doesn't add comment prefix.
 2916        cx.set_state(indoc! {"
 2917        /*ˇ*
 2918    "});
 2919        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2920        cx.assert_editor_state(indoc! {"
 2921        /*
 2922        ˇ*
 2923    "});
 2924        // Ensure that if suffix exists on same line after cursor it adds new line.
 2925        cx.set_state(indoc! {"
 2926        /**ˇ*/
 2927    "});
 2928        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2929        cx.assert_editor_state(indoc! {"
 2930        /**
 2931         * ˇ
 2932         */
 2933    "});
 2934        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2935        cx.set_state(indoc! {"
 2936        /**ˇ */
 2937    "});
 2938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2939        cx.assert_editor_state(indoc! {"
 2940        /**
 2941         * ˇ
 2942         */
 2943    "});
 2944        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2945        cx.set_state(indoc! {"
 2946        /** ˇ*/
 2947    "});
 2948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2949        cx.assert_editor_state(
 2950            indoc! {"
 2951        /**s
 2952         * ˇ
 2953         */
 2954    "}
 2955            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2956            .as_str(),
 2957        );
 2958        // Ensure that delimiter space is preserved when newline on already
 2959        // spaced delimiter.
 2960        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2961        cx.assert_editor_state(
 2962            indoc! {"
 2963        /**s
 2964         *s
 2965         * ˇ
 2966         */
 2967    "}
 2968            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2969            .as_str(),
 2970        );
 2971        // Ensure that delimiter space is preserved when space is not
 2972        // on existing delimiter.
 2973        cx.set_state(indoc! {"
 2974        /**
 2975 2976         */
 2977    "});
 2978        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2979        cx.assert_editor_state(indoc! {"
 2980        /**
 2981         *
 2982         * ˇ
 2983         */
 2984    "});
 2985        // Ensure that if suffix exists on same line after cursor it
 2986        // doesn't add extra new line if prefix is not on same line.
 2987        cx.set_state(indoc! {"
 2988        /**
 2989        ˇ*/
 2990    "});
 2991        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2992        cx.assert_editor_state(indoc! {"
 2993        /**
 2994
 2995        ˇ*/
 2996    "});
 2997        // Ensure that it detects suffix after existing prefix.
 2998        cx.set_state(indoc! {"
 2999        /**ˇ/
 3000    "});
 3001        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3002        cx.assert_editor_state(indoc! {"
 3003        /**
 3004        ˇ/
 3005    "});
 3006        // Ensure that if suffix exists on same line before
 3007        // cursor it does not add comment prefix.
 3008        cx.set_state(indoc! {"
 3009        /** */ˇ
 3010    "});
 3011        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3012        cx.assert_editor_state(indoc! {"
 3013        /** */
 3014        ˇ
 3015    "});
 3016        // Ensure that if suffix exists on same line before
 3017        // cursor it does not add comment prefix.
 3018        cx.set_state(indoc! {"
 3019        /**
 3020         *
 3021         */ˇ
 3022    "});
 3023        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3024        cx.assert_editor_state(indoc! {"
 3025        /**
 3026         *
 3027         */
 3028         ˇ
 3029    "});
 3030
 3031        // Ensure that inline comment followed by code
 3032        // doesn't add comment prefix on newline
 3033        cx.set_state(indoc! {"
 3034        /** */ textˇ
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /** */ text
 3039        ˇ
 3040    "});
 3041
 3042        // Ensure that text after comment end tag
 3043        // doesn't add comment prefix on newline
 3044        cx.set_state(indoc! {"
 3045        /**
 3046         *
 3047         */ˇtext
 3048    "});
 3049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3050        cx.assert_editor_state(indoc! {"
 3051        /**
 3052         *
 3053         */
 3054         ˇtext
 3055    "});
 3056
 3057        // Ensure if not comment block it doesn't
 3058        // add comment prefix on newline
 3059        cx.set_state(indoc! {"
 3060        * textˇ
 3061    "});
 3062        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3063        cx.assert_editor_state(indoc! {"
 3064        * text
 3065        ˇ
 3066    "});
 3067    }
 3068    // Ensure that comment continuations can be disabled.
 3069    update_test_language_settings(cx, |settings| {
 3070        settings.defaults.extend_comment_on_newline = Some(false);
 3071    });
 3072    let mut cx = EditorTestContext::new(cx).await;
 3073    cx.set_state(indoc! {"
 3074        /**ˇ
 3075    "});
 3076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3077    cx.assert_editor_state(indoc! {"
 3078        /**
 3079        ˇ
 3080    "});
 3081}
 3082
 3083#[gpui::test]
 3084fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3089        let mut editor = build_editor(buffer.clone(), window, cx);
 3090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3091            s.select_ranges([3..4, 11..12, 19..20])
 3092        });
 3093        editor
 3094    });
 3095
 3096    _ = editor.update(cx, |editor, window, cx| {
 3097        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3098        editor.buffer.update(cx, |buffer, cx| {
 3099            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3100            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3101        });
 3102        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3103
 3104        editor.insert("Z", window, cx);
 3105        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3106
 3107        // The selections are moved after the inserted characters
 3108        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3109    });
 3110}
 3111
 3112#[gpui::test]
 3113async fn test_tab(cx: &mut TestAppContext) {
 3114    init_test(cx, |settings| {
 3115        settings.defaults.tab_size = NonZeroU32::new(3)
 3116    });
 3117
 3118    let mut cx = EditorTestContext::new(cx).await;
 3119    cx.set_state(indoc! {"
 3120        ˇabˇc
 3121        ˇ🏀ˇ🏀ˇefg
 3122 3123    "});
 3124    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3125    cx.assert_editor_state(indoc! {"
 3126           ˇab ˇc
 3127           ˇ🏀  ˇ🏀  ˇefg
 3128        d  ˇ
 3129    "});
 3130
 3131    cx.set_state(indoc! {"
 3132        a
 3133        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3134    "});
 3135    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3136    cx.assert_editor_state(indoc! {"
 3137        a
 3138           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3144    init_test(cx, |_| {});
 3145
 3146    let mut cx = EditorTestContext::new(cx).await;
 3147    let language = Arc::new(
 3148        Language::new(
 3149            LanguageConfig::default(),
 3150            Some(tree_sitter_rust::LANGUAGE.into()),
 3151        )
 3152        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3153        .unwrap(),
 3154    );
 3155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3156
 3157    // test when all cursors are not at suggested indent
 3158    // then simply move to their suggested indent location
 3159    cx.set_state(indoc! {"
 3160        const a: B = (
 3161            c(
 3162        ˇ
 3163        ˇ    )
 3164        );
 3165    "});
 3166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3167    cx.assert_editor_state(indoc! {"
 3168        const a: B = (
 3169            c(
 3170                ˇ
 3171            ˇ)
 3172        );
 3173    "});
 3174
 3175    // test cursor already at suggested indent not moving when
 3176    // other cursors are yet to reach their suggested indents
 3177    cx.set_state(indoc! {"
 3178        ˇ
 3179        const a: B = (
 3180            c(
 3181                d(
 3182        ˇ
 3183                )
 3184        ˇ
 3185        ˇ    )
 3186        );
 3187    "});
 3188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3189    cx.assert_editor_state(indoc! {"
 3190        ˇ
 3191        const a: B = (
 3192            c(
 3193                d(
 3194                    ˇ
 3195                )
 3196                ˇ
 3197            ˇ)
 3198        );
 3199    "});
 3200    // test when all cursors are at suggested indent then tab is inserted
 3201    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3202    cx.assert_editor_state(indoc! {"
 3203            ˇ
 3204        const a: B = (
 3205            c(
 3206                d(
 3207                        ˇ
 3208                )
 3209                    ˇ
 3210                ˇ)
 3211        );
 3212    "});
 3213
 3214    // test when current indent is less than suggested indent,
 3215    // we adjust line to match suggested indent and move cursor to it
 3216    //
 3217    // when no other cursor is at word boundary, all of them should move
 3218    cx.set_state(indoc! {"
 3219        const a: B = (
 3220            c(
 3221                d(
 3222        ˇ
 3223        ˇ   )
 3224        ˇ   )
 3225        );
 3226    "});
 3227    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3228    cx.assert_editor_state(indoc! {"
 3229        const a: B = (
 3230            c(
 3231                d(
 3232                    ˇ
 3233                ˇ)
 3234            ˇ)
 3235        );
 3236    "});
 3237
 3238    // test when current indent is less than suggested indent,
 3239    // we adjust line to match suggested indent and move cursor to it
 3240    //
 3241    // when some other cursor is at word boundary, it should not move
 3242    cx.set_state(indoc! {"
 3243        const a: B = (
 3244            c(
 3245                d(
 3246        ˇ
 3247        ˇ   )
 3248           ˇ)
 3249        );
 3250    "});
 3251    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3252    cx.assert_editor_state(indoc! {"
 3253        const a: B = (
 3254            c(
 3255                d(
 3256                    ˇ
 3257                ˇ)
 3258            ˇ)
 3259        );
 3260    "});
 3261
 3262    // test when current indent is more than suggested indent,
 3263    // we just move cursor to current indent instead of suggested indent
 3264    //
 3265    // when no other cursor is at word boundary, all of them should move
 3266    cx.set_state(indoc! {"
 3267        const a: B = (
 3268            c(
 3269                d(
 3270        ˇ
 3271        ˇ                )
 3272        ˇ   )
 3273        );
 3274    "});
 3275    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3276    cx.assert_editor_state(indoc! {"
 3277        const a: B = (
 3278            c(
 3279                d(
 3280                    ˇ
 3281                        ˇ)
 3282            ˇ)
 3283        );
 3284    "});
 3285    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        const a: B = (
 3288            c(
 3289                d(
 3290                        ˇ
 3291                            ˇ)
 3292                ˇ)
 3293        );
 3294    "});
 3295
 3296    // test when current indent is more than suggested indent,
 3297    // we just move cursor to current indent instead of suggested indent
 3298    //
 3299    // when some other cursor is at word boundary, it doesn't move
 3300    cx.set_state(indoc! {"
 3301        const a: B = (
 3302            c(
 3303                d(
 3304        ˇ
 3305        ˇ                )
 3306            ˇ)
 3307        );
 3308    "});
 3309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3310    cx.assert_editor_state(indoc! {"
 3311        const a: B = (
 3312            c(
 3313                d(
 3314                    ˇ
 3315                        ˇ)
 3316            ˇ)
 3317        );
 3318    "});
 3319
 3320    // handle auto-indent when there are multiple cursors on the same line
 3321    cx.set_state(indoc! {"
 3322        const a: B = (
 3323            c(
 3324        ˇ    ˇ
 3325        ˇ    )
 3326        );
 3327    "});
 3328    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3329    cx.assert_editor_state(indoc! {"
 3330        const a: B = (
 3331            c(
 3332                ˇ
 3333            ˇ)
 3334        );
 3335    "});
 3336}
 3337
 3338#[gpui::test]
 3339async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3340    init_test(cx, |settings| {
 3341        settings.defaults.tab_size = NonZeroU32::new(3)
 3342    });
 3343
 3344    let mut cx = EditorTestContext::new(cx).await;
 3345    cx.set_state(indoc! {"
 3346         ˇ
 3347        \t ˇ
 3348        \t  ˇ
 3349        \t   ˇ
 3350         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3351    "});
 3352
 3353    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3354    cx.assert_editor_state(indoc! {"
 3355           ˇ
 3356        \t   ˇ
 3357        \t   ˇ
 3358        \t      ˇ
 3359         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3360    "});
 3361}
 3362
 3363#[gpui::test]
 3364async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3365    init_test(cx, |settings| {
 3366        settings.defaults.tab_size = NonZeroU32::new(4)
 3367    });
 3368
 3369    let language = Arc::new(
 3370        Language::new(
 3371            LanguageConfig::default(),
 3372            Some(tree_sitter_rust::LANGUAGE.into()),
 3373        )
 3374        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3375        .unwrap(),
 3376    );
 3377
 3378    let mut cx = EditorTestContext::new(cx).await;
 3379    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3380    cx.set_state(indoc! {"
 3381        fn a() {
 3382            if b {
 3383        \t ˇc
 3384            }
 3385        }
 3386    "});
 3387
 3388    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3389    cx.assert_editor_state(indoc! {"
 3390        fn a() {
 3391            if b {
 3392                ˇc
 3393            }
 3394        }
 3395    "});
 3396}
 3397
 3398#[gpui::test]
 3399async fn test_indent_outdent(cx: &mut TestAppContext) {
 3400    init_test(cx, |settings| {
 3401        settings.defaults.tab_size = NonZeroU32::new(4);
 3402    });
 3403
 3404    let mut cx = EditorTestContext::new(cx).await;
 3405
 3406    cx.set_state(indoc! {"
 3407          «oneˇ» «twoˇ»
 3408        three
 3409         four
 3410    "});
 3411    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3412    cx.assert_editor_state(indoc! {"
 3413            «oneˇ» «twoˇ»
 3414        three
 3415         four
 3416    "});
 3417
 3418    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3419    cx.assert_editor_state(indoc! {"
 3420        «oneˇ» «twoˇ»
 3421        three
 3422         four
 3423    "});
 3424
 3425    // select across line ending
 3426    cx.set_state(indoc! {"
 3427        one two
 3428        t«hree
 3429        ˇ» four
 3430    "});
 3431    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3432    cx.assert_editor_state(indoc! {"
 3433        one two
 3434            t«hree
 3435        ˇ» four
 3436    "});
 3437
 3438    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3439    cx.assert_editor_state(indoc! {"
 3440        one two
 3441        t«hree
 3442        ˇ» four
 3443    "});
 3444
 3445    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3446    cx.set_state(indoc! {"
 3447        one two
 3448        ˇthree
 3449            four
 3450    "});
 3451    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3452    cx.assert_editor_state(indoc! {"
 3453        one two
 3454            ˇthree
 3455            four
 3456    "});
 3457
 3458    cx.set_state(indoc! {"
 3459        one two
 3460        ˇ    three
 3461            four
 3462    "});
 3463    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3464    cx.assert_editor_state(indoc! {"
 3465        one two
 3466        ˇthree
 3467            four
 3468    "});
 3469}
 3470
 3471#[gpui::test]
 3472async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3473    init_test(cx, |settings| {
 3474        settings.defaults.hard_tabs = Some(true);
 3475    });
 3476
 3477    let mut cx = EditorTestContext::new(cx).await;
 3478
 3479    // select two ranges on one line
 3480    cx.set_state(indoc! {"
 3481        «oneˇ» «twoˇ»
 3482        three
 3483        four
 3484    "});
 3485    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3486    cx.assert_editor_state(indoc! {"
 3487        \t«oneˇ» «twoˇ»
 3488        three
 3489        four
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        \t\t«oneˇ» «twoˇ»
 3494        three
 3495        four
 3496    "});
 3497    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3498    cx.assert_editor_state(indoc! {"
 3499        \t«oneˇ» «twoˇ»
 3500        three
 3501        four
 3502    "});
 3503    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3504    cx.assert_editor_state(indoc! {"
 3505        «oneˇ» «twoˇ»
 3506        three
 3507        four
 3508    "});
 3509
 3510    // select across a line ending
 3511    cx.set_state(indoc! {"
 3512        one two
 3513        t«hree
 3514        ˇ»four
 3515    "});
 3516    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3517    cx.assert_editor_state(indoc! {"
 3518        one two
 3519        \tt«hree
 3520        ˇ»four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525        \t\tt«hree
 3526        ˇ»four
 3527    "});
 3528    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3529    cx.assert_editor_state(indoc! {"
 3530        one two
 3531        \tt«hree
 3532        ˇ»four
 3533    "});
 3534    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3535    cx.assert_editor_state(indoc! {"
 3536        one two
 3537        t«hree
 3538        ˇ»four
 3539    "});
 3540
 3541    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3542    cx.set_state(indoc! {"
 3543        one two
 3544        ˇthree
 3545        four
 3546    "});
 3547    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3548    cx.assert_editor_state(indoc! {"
 3549        one two
 3550        ˇthree
 3551        four
 3552    "});
 3553    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3554    cx.assert_editor_state(indoc! {"
 3555        one two
 3556        \tˇthree
 3557        four
 3558    "});
 3559    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3560    cx.assert_editor_state(indoc! {"
 3561        one two
 3562        ˇthree
 3563        four
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3569    init_test(cx, |settings| {
 3570        settings.languages.0.extend([
 3571            (
 3572                "TOML".into(),
 3573                LanguageSettingsContent {
 3574                    tab_size: NonZeroU32::new(2),
 3575                    ..Default::default()
 3576                },
 3577            ),
 3578            (
 3579                "Rust".into(),
 3580                LanguageSettingsContent {
 3581                    tab_size: NonZeroU32::new(4),
 3582                    ..Default::default()
 3583                },
 3584            ),
 3585        ]);
 3586    });
 3587
 3588    let toml_language = Arc::new(Language::new(
 3589        LanguageConfig {
 3590            name: "TOML".into(),
 3591            ..Default::default()
 3592        },
 3593        None,
 3594    ));
 3595    let rust_language = Arc::new(Language::new(
 3596        LanguageConfig {
 3597            name: "Rust".into(),
 3598            ..Default::default()
 3599        },
 3600        None,
 3601    ));
 3602
 3603    let toml_buffer =
 3604        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3605    let rust_buffer =
 3606        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3607    let multibuffer = cx.new(|cx| {
 3608        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3609        multibuffer.push_excerpts(
 3610            toml_buffer.clone(),
 3611            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3612            cx,
 3613        );
 3614        multibuffer.push_excerpts(
 3615            rust_buffer.clone(),
 3616            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3617            cx,
 3618        );
 3619        multibuffer
 3620    });
 3621
 3622    cx.add_window(|window, cx| {
 3623        let mut editor = build_editor(multibuffer, window, cx);
 3624
 3625        assert_eq!(
 3626            editor.text(cx),
 3627            indoc! {"
 3628                a = 1
 3629                b = 2
 3630
 3631                const c: usize = 3;
 3632            "}
 3633        );
 3634
 3635        select_ranges(
 3636            &mut editor,
 3637            indoc! {"
 3638                «aˇ» = 1
 3639                b = 2
 3640
 3641                «const c:ˇ» usize = 3;
 3642            "},
 3643            window,
 3644            cx,
 3645        );
 3646
 3647        editor.tab(&Tab, window, cx);
 3648        assert_text_with_selections(
 3649            &mut editor,
 3650            indoc! {"
 3651                  «aˇ» = 1
 3652                b = 2
 3653
 3654                    «const c:ˇ» usize = 3;
 3655            "},
 3656            cx,
 3657        );
 3658        editor.backtab(&Backtab, window, cx);
 3659        assert_text_with_selections(
 3660            &mut editor,
 3661            indoc! {"
 3662                «aˇ» = 1
 3663                b = 2
 3664
 3665                «const c:ˇ» usize = 3;
 3666            "},
 3667            cx,
 3668        );
 3669
 3670        editor
 3671    });
 3672}
 3673
 3674#[gpui::test]
 3675async fn test_backspace(cx: &mut TestAppContext) {
 3676    init_test(cx, |_| {});
 3677
 3678    let mut cx = EditorTestContext::new(cx).await;
 3679
 3680    // Basic backspace
 3681    cx.set_state(indoc! {"
 3682        onˇe two three
 3683        fou«rˇ» five six
 3684        seven «ˇeight nine
 3685        »ten
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        oˇe two three
 3690        fouˇ five six
 3691        seven ˇten
 3692    "});
 3693
 3694    // Test backspace inside and around indents
 3695    cx.set_state(indoc! {"
 3696        zero
 3697            ˇone
 3698                ˇtwo
 3699            ˇ ˇ ˇ  three
 3700        ˇ  ˇ  four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        zero
 3705        ˇone
 3706            ˇtwo
 3707        ˇ  threeˇ  four
 3708    "});
 3709}
 3710
 3711#[gpui::test]
 3712async fn test_delete(cx: &mut TestAppContext) {
 3713    init_test(cx, |_| {});
 3714
 3715    let mut cx = EditorTestContext::new(cx).await;
 3716    cx.set_state(indoc! {"
 3717        onˇe two three
 3718        fou«rˇ» five six
 3719        seven «ˇeight nine
 3720        »ten
 3721    "});
 3722    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3723    cx.assert_editor_state(indoc! {"
 3724        onˇ two three
 3725        fouˇ five six
 3726        seven ˇten
 3727    "});
 3728}
 3729
 3730#[gpui::test]
 3731fn test_delete_line(cx: &mut TestAppContext) {
 3732    init_test(cx, |_| {});
 3733
 3734    let editor = cx.add_window(|window, cx| {
 3735        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3736        build_editor(buffer, window, cx)
 3737    });
 3738    _ = editor.update(cx, |editor, window, cx| {
 3739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3740            s.select_display_ranges([
 3741                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3742                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3743                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3744            ])
 3745        });
 3746        editor.delete_line(&DeleteLine, window, cx);
 3747        assert_eq!(editor.display_text(cx), "ghi");
 3748        assert_eq!(
 3749            editor.selections.display_ranges(cx),
 3750            vec![
 3751                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3752                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3753            ]
 3754        );
 3755    });
 3756
 3757    let editor = cx.add_window(|window, cx| {
 3758        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3759        build_editor(buffer, window, cx)
 3760    });
 3761    _ = editor.update(cx, |editor, window, cx| {
 3762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3763            s.select_display_ranges([
 3764                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3765            ])
 3766        });
 3767        editor.delete_line(&DeleteLine, window, cx);
 3768        assert_eq!(editor.display_text(cx), "ghi\n");
 3769        assert_eq!(
 3770            editor.selections.display_ranges(cx),
 3771            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3772        );
 3773    });
 3774}
 3775
 3776#[gpui::test]
 3777fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3778    init_test(cx, |_| {});
 3779
 3780    cx.add_window(|window, cx| {
 3781        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3782        let mut editor = build_editor(buffer.clone(), window, cx);
 3783        let buffer = buffer.read(cx).as_singleton().unwrap();
 3784
 3785        assert_eq!(
 3786            editor.selections.ranges::<Point>(cx),
 3787            &[Point::new(0, 0)..Point::new(0, 0)]
 3788        );
 3789
 3790        // When on single line, replace newline at end by space
 3791        editor.join_lines(&JoinLines, window, cx);
 3792        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3793        assert_eq!(
 3794            editor.selections.ranges::<Point>(cx),
 3795            &[Point::new(0, 3)..Point::new(0, 3)]
 3796        );
 3797
 3798        // When multiple lines are selected, remove newlines that are spanned by the selection
 3799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3800            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3801        });
 3802        editor.join_lines(&JoinLines, window, cx);
 3803        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3804        assert_eq!(
 3805            editor.selections.ranges::<Point>(cx),
 3806            &[Point::new(0, 11)..Point::new(0, 11)]
 3807        );
 3808
 3809        // Undo should be transactional
 3810        editor.undo(&Undo, window, cx);
 3811        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3812        assert_eq!(
 3813            editor.selections.ranges::<Point>(cx),
 3814            &[Point::new(0, 5)..Point::new(2, 2)]
 3815        );
 3816
 3817        // When joining an empty line don't insert a space
 3818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3819            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3820        });
 3821        editor.join_lines(&JoinLines, window, cx);
 3822        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3823        assert_eq!(
 3824            editor.selections.ranges::<Point>(cx),
 3825            [Point::new(2, 3)..Point::new(2, 3)]
 3826        );
 3827
 3828        // We can remove trailing newlines
 3829        editor.join_lines(&JoinLines, window, cx);
 3830        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3831        assert_eq!(
 3832            editor.selections.ranges::<Point>(cx),
 3833            [Point::new(2, 3)..Point::new(2, 3)]
 3834        );
 3835
 3836        // We don't blow up on the last line
 3837        editor.join_lines(&JoinLines, window, cx);
 3838        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3839        assert_eq!(
 3840            editor.selections.ranges::<Point>(cx),
 3841            [Point::new(2, 3)..Point::new(2, 3)]
 3842        );
 3843
 3844        // reset to test indentation
 3845        editor.buffer.update(cx, |buffer, cx| {
 3846            buffer.edit(
 3847                [
 3848                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3849                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3850                ],
 3851                None,
 3852                cx,
 3853            )
 3854        });
 3855
 3856        // We remove any leading spaces
 3857        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3859            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3860        });
 3861        editor.join_lines(&JoinLines, window, cx);
 3862        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3863
 3864        // We don't insert a space for a line containing only spaces
 3865        editor.join_lines(&JoinLines, window, cx);
 3866        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3867
 3868        // We ignore any leading tabs
 3869        editor.join_lines(&JoinLines, window, cx);
 3870        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3871
 3872        editor
 3873    });
 3874}
 3875
 3876#[gpui::test]
 3877fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3878    init_test(cx, |_| {});
 3879
 3880    cx.add_window(|window, cx| {
 3881        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3882        let mut editor = build_editor(buffer.clone(), window, cx);
 3883        let buffer = buffer.read(cx).as_singleton().unwrap();
 3884
 3885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3886            s.select_ranges([
 3887                Point::new(0, 2)..Point::new(1, 1),
 3888                Point::new(1, 2)..Point::new(1, 2),
 3889                Point::new(3, 1)..Point::new(3, 2),
 3890            ])
 3891        });
 3892
 3893        editor.join_lines(&JoinLines, window, cx);
 3894        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3895
 3896        assert_eq!(
 3897            editor.selections.ranges::<Point>(cx),
 3898            [
 3899                Point::new(0, 7)..Point::new(0, 7),
 3900                Point::new(1, 3)..Point::new(1, 3)
 3901            ]
 3902        );
 3903        editor
 3904    });
 3905}
 3906
 3907#[gpui::test]
 3908async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3909    init_test(cx, |_| {});
 3910
 3911    let mut cx = EditorTestContext::new(cx).await;
 3912
 3913    let diff_base = r#"
 3914        Line 0
 3915        Line 1
 3916        Line 2
 3917        Line 3
 3918        "#
 3919    .unindent();
 3920
 3921    cx.set_state(
 3922        &r#"
 3923        ˇLine 0
 3924        Line 1
 3925        Line 2
 3926        Line 3
 3927        "#
 3928        .unindent(),
 3929    );
 3930
 3931    cx.set_head_text(&diff_base);
 3932    executor.run_until_parked();
 3933
 3934    // Join lines
 3935    cx.update_editor(|editor, window, cx| {
 3936        editor.join_lines(&JoinLines, window, cx);
 3937    });
 3938    executor.run_until_parked();
 3939
 3940    cx.assert_editor_state(
 3941        &r#"
 3942        Line 0ˇ Line 1
 3943        Line 2
 3944        Line 3
 3945        "#
 3946        .unindent(),
 3947    );
 3948    // Join again
 3949    cx.update_editor(|editor, window, cx| {
 3950        editor.join_lines(&JoinLines, window, cx);
 3951    });
 3952    executor.run_until_parked();
 3953
 3954    cx.assert_editor_state(
 3955        &r#"
 3956        Line 0 Line 1ˇ Line 2
 3957        Line 3
 3958        "#
 3959        .unindent(),
 3960    );
 3961}
 3962
 3963#[gpui::test]
 3964async fn test_custom_newlines_cause_no_false_positive_diffs(
 3965    executor: BackgroundExecutor,
 3966    cx: &mut TestAppContext,
 3967) {
 3968    init_test(cx, |_| {});
 3969    let mut cx = EditorTestContext::new(cx).await;
 3970    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3971    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3972    executor.run_until_parked();
 3973
 3974    cx.update_editor(|editor, window, cx| {
 3975        let snapshot = editor.snapshot(window, cx);
 3976        assert_eq!(
 3977            snapshot
 3978                .buffer_snapshot
 3979                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3980                .collect::<Vec<_>>(),
 3981            Vec::new(),
 3982            "Should not have any diffs for files with custom newlines"
 3983        );
 3984    });
 3985}
 3986
 3987#[gpui::test]
 3988async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 3989    init_test(cx, |_| {});
 3990
 3991    let mut cx = EditorTestContext::new(cx).await;
 3992
 3993    // Test sort_lines_case_insensitive()
 3994    cx.set_state(indoc! {"
 3995        «z
 3996        y
 3997        x
 3998        Z
 3999        Y
 4000        Xˇ»
 4001    "});
 4002    cx.update_editor(|e, window, cx| {
 4003        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4004    });
 4005    cx.assert_editor_state(indoc! {"
 4006        «x
 4007        X
 4008        y
 4009        Y
 4010        z
 4011        Zˇ»
 4012    "});
 4013
 4014    // Test reverse_lines()
 4015    cx.set_state(indoc! {"
 4016        «5
 4017        4
 4018        3
 4019        2
 4020        1ˇ»
 4021    "});
 4022    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4023    cx.assert_editor_state(indoc! {"
 4024        «1
 4025        2
 4026        3
 4027        4
 4028        5ˇ»
 4029    "});
 4030
 4031    // Skip testing shuffle_line()
 4032
 4033    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4034    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4035
 4036    // Don't manipulate when cursor is on single line, but expand the selection
 4037    cx.set_state(indoc! {"
 4038        ddˇdd
 4039        ccc
 4040        bb
 4041        a
 4042    "});
 4043    cx.update_editor(|e, window, cx| {
 4044        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4045    });
 4046    cx.assert_editor_state(indoc! {"
 4047        «ddddˇ»
 4048        ccc
 4049        bb
 4050        a
 4051    "});
 4052
 4053    // Basic manipulate case
 4054    // Start selection moves to column 0
 4055    // End of selection shrinks to fit shorter line
 4056    cx.set_state(indoc! {"
 4057        dd«d
 4058        ccc
 4059        bb
 4060        aaaaaˇ»
 4061    "});
 4062    cx.update_editor(|e, window, cx| {
 4063        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4064    });
 4065    cx.assert_editor_state(indoc! {"
 4066        «aaaaa
 4067        bb
 4068        ccc
 4069        dddˇ»
 4070    "});
 4071
 4072    // Manipulate case with newlines
 4073    cx.set_state(indoc! {"
 4074        dd«d
 4075        ccc
 4076
 4077        bb
 4078        aaaaa
 4079
 4080        ˇ»
 4081    "});
 4082    cx.update_editor(|e, window, cx| {
 4083        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4084    });
 4085    cx.assert_editor_state(indoc! {"
 4086        «
 4087
 4088        aaaaa
 4089        bb
 4090        ccc
 4091        dddˇ»
 4092
 4093    "});
 4094
 4095    // Adding new line
 4096    cx.set_state(indoc! {"
 4097        aa«a
 4098        bbˇ»b
 4099    "});
 4100    cx.update_editor(|e, window, cx| {
 4101        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4102    });
 4103    cx.assert_editor_state(indoc! {"
 4104        «aaa
 4105        bbb
 4106        added_lineˇ»
 4107    "});
 4108
 4109    // Removing line
 4110    cx.set_state(indoc! {"
 4111        aa«a
 4112        bbbˇ»
 4113    "});
 4114    cx.update_editor(|e, window, cx| {
 4115        e.manipulate_immutable_lines(window, cx, |lines| {
 4116            lines.pop();
 4117        })
 4118    });
 4119    cx.assert_editor_state(indoc! {"
 4120        «aaaˇ»
 4121    "});
 4122
 4123    // Removing all lines
 4124    cx.set_state(indoc! {"
 4125        aa«a
 4126        bbbˇ»
 4127    "});
 4128    cx.update_editor(|e, window, cx| {
 4129        e.manipulate_immutable_lines(window, cx, |lines| {
 4130            lines.drain(..);
 4131        })
 4132    });
 4133    cx.assert_editor_state(indoc! {"
 4134        ˇ
 4135    "});
 4136}
 4137
 4138#[gpui::test]
 4139async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4140    init_test(cx, |_| {});
 4141
 4142    let mut cx = EditorTestContext::new(cx).await;
 4143
 4144    // Consider continuous selection as single selection
 4145    cx.set_state(indoc! {"
 4146        Aaa«aa
 4147        cˇ»c«c
 4148        bb
 4149        aaaˇ»aa
 4150    "});
 4151    cx.update_editor(|e, window, cx| {
 4152        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4153    });
 4154    cx.assert_editor_state(indoc! {"
 4155        «Aaaaa
 4156        ccc
 4157        bb
 4158        aaaaaˇ»
 4159    "});
 4160
 4161    cx.set_state(indoc! {"
 4162        Aaa«aa
 4163        cˇ»c«c
 4164        bb
 4165        aaaˇ»aa
 4166    "});
 4167    cx.update_editor(|e, window, cx| {
 4168        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4169    });
 4170    cx.assert_editor_state(indoc! {"
 4171        «Aaaaa
 4172        ccc
 4173        bbˇ»
 4174    "});
 4175
 4176    // Consider non continuous selection as distinct dedup operations
 4177    cx.set_state(indoc! {"
 4178        «aaaaa
 4179        bb
 4180        aaaaa
 4181        aaaaaˇ»
 4182
 4183        aaa«aaˇ»
 4184    "});
 4185    cx.update_editor(|e, window, cx| {
 4186        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4187    });
 4188    cx.assert_editor_state(indoc! {"
 4189        «aaaaa
 4190        bbˇ»
 4191
 4192        «aaaaaˇ»
 4193    "});
 4194}
 4195
 4196#[gpui::test]
 4197async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4198    init_test(cx, |_| {});
 4199
 4200    let mut cx = EditorTestContext::new(cx).await;
 4201
 4202    cx.set_state(indoc! {"
 4203        «Aaa
 4204        aAa
 4205        Aaaˇ»
 4206    "});
 4207    cx.update_editor(|e, window, cx| {
 4208        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4209    });
 4210    cx.assert_editor_state(indoc! {"
 4211        «Aaa
 4212        aAaˇ»
 4213    "});
 4214
 4215    cx.set_state(indoc! {"
 4216        «Aaa
 4217        aAa
 4218        aaAˇ»
 4219    "});
 4220    cx.update_editor(|e, window, cx| {
 4221        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4222    });
 4223    cx.assert_editor_state(indoc! {"
 4224        «Aaaˇ»
 4225    "});
 4226}
 4227
 4228#[gpui::test]
 4229async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4230    init_test(cx, |_| {});
 4231
 4232    let mut cx = EditorTestContext::new(cx).await;
 4233
 4234    // Manipulate with multiple selections on a single line
 4235    cx.set_state(indoc! {"
 4236        dd«dd
 4237        cˇ»c«c
 4238        bb
 4239        aaaˇ»aa
 4240    "});
 4241    cx.update_editor(|e, window, cx| {
 4242        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4243    });
 4244    cx.assert_editor_state(indoc! {"
 4245        «aaaaa
 4246        bb
 4247        ccc
 4248        ddddˇ»
 4249    "});
 4250
 4251    // Manipulate with multiple disjoin selections
 4252    cx.set_state(indoc! {"
 4253 4254        4
 4255        3
 4256        2
 4257        1ˇ»
 4258
 4259        dd«dd
 4260        ccc
 4261        bb
 4262        aaaˇ»aa
 4263    "});
 4264    cx.update_editor(|e, window, cx| {
 4265        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4266    });
 4267    cx.assert_editor_state(indoc! {"
 4268        «1
 4269        2
 4270        3
 4271        4
 4272        5ˇ»
 4273
 4274        «aaaaa
 4275        bb
 4276        ccc
 4277        ddddˇ»
 4278    "});
 4279
 4280    // Adding lines on each selection
 4281    cx.set_state(indoc! {"
 4282 4283        1ˇ»
 4284
 4285        bb«bb
 4286        aaaˇ»aa
 4287    "});
 4288    cx.update_editor(|e, window, cx| {
 4289        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4290    });
 4291    cx.assert_editor_state(indoc! {"
 4292        «2
 4293        1
 4294        added lineˇ»
 4295
 4296        «bbbb
 4297        aaaaa
 4298        added lineˇ»
 4299    "});
 4300
 4301    // Removing lines on each selection
 4302    cx.set_state(indoc! {"
 4303 4304        1ˇ»
 4305
 4306        bb«bb
 4307        aaaˇ»aa
 4308    "});
 4309    cx.update_editor(|e, window, cx| {
 4310        e.manipulate_immutable_lines(window, cx, |lines| {
 4311            lines.pop();
 4312        })
 4313    });
 4314    cx.assert_editor_state(indoc! {"
 4315        «2ˇ»
 4316
 4317        «bbbbˇ»
 4318    "});
 4319}
 4320
 4321#[gpui::test]
 4322async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4323    init_test(cx, |settings| {
 4324        settings.defaults.tab_size = NonZeroU32::new(3)
 4325    });
 4326
 4327    let mut cx = EditorTestContext::new(cx).await;
 4328
 4329    // MULTI SELECTION
 4330    // Ln.1 "«" tests empty lines
 4331    // Ln.9 tests just leading whitespace
 4332    cx.set_state(indoc! {"
 4333        «
 4334        abc                 // No indentationˇ»
 4335        «\tabc              // 1 tabˇ»
 4336        \t\tabc «      ˇ»   // 2 tabs
 4337        \t ab«c             // Tab followed by space
 4338         \tabc              // Space followed by tab (3 spaces should be the result)
 4339        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4340           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4341        \t
 4342        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4343    "});
 4344    cx.update_editor(|e, window, cx| {
 4345        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4346    });
 4347    cx.assert_editor_state(
 4348        indoc! {"
 4349            «
 4350            abc                 // No indentation
 4351               abc              // 1 tab
 4352                  abc          // 2 tabs
 4353                abc             // Tab followed by space
 4354               abc              // Space followed by tab (3 spaces should be the result)
 4355                           abc   // Mixed indentation (tab conversion depends on the column)
 4356               abc         // Already space indented
 4357               ·
 4358               abc\tdef          // Only the leading tab is manipulatedˇ»
 4359        "}
 4360        .replace("·", "")
 4361        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4362    );
 4363
 4364    // Test on just a few lines, the others should remain unchanged
 4365    // Only lines (3, 5, 10, 11) should change
 4366    cx.set_state(
 4367        indoc! {"
 4368            ·
 4369            abc                 // No indentation
 4370            \tabcˇ               // 1 tab
 4371            \t\tabc             // 2 tabs
 4372            \t abcˇ              // Tab followed by space
 4373             \tabc              // Space followed by tab (3 spaces should be the result)
 4374            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4375               abc              // Already space indented
 4376            «\t
 4377            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4378        "}
 4379        .replace("·", "")
 4380        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4381    );
 4382    cx.update_editor(|e, window, cx| {
 4383        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4384    });
 4385    cx.assert_editor_state(
 4386        indoc! {"
 4387            ·
 4388            abc                 // No indentation
 4389            «   abc               // 1 tabˇ»
 4390            \t\tabc             // 2 tabs
 4391            «    abc              // Tab followed by spaceˇ»
 4392             \tabc              // Space followed by tab (3 spaces should be the result)
 4393            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4394               abc              // Already space indented
 4395            «   ·
 4396               abc\tdef          // Only the leading tab is manipulatedˇ»
 4397        "}
 4398        .replace("·", "")
 4399        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4400    );
 4401
 4402    // SINGLE SELECTION
 4403    // Ln.1 "«" tests empty lines
 4404    // Ln.9 tests just leading whitespace
 4405    cx.set_state(indoc! {"
 4406        «
 4407        abc                 // No indentation
 4408        \tabc               // 1 tab
 4409        \t\tabc             // 2 tabs
 4410        \t abc              // Tab followed by space
 4411         \tabc              // Space followed by tab (3 spaces should be the result)
 4412        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4413           abc              // Already space indented
 4414        \t
 4415        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4419    });
 4420    cx.assert_editor_state(
 4421        indoc! {"
 4422            «
 4423            abc                 // No indentation
 4424               abc               // 1 tab
 4425                  abc             // 2 tabs
 4426                abc              // Tab followed by space
 4427               abc              // Space followed by tab (3 spaces should be the result)
 4428                           abc   // Mixed indentation (tab conversion depends on the column)
 4429               abc              // Already space indented
 4430               ·
 4431               abc\tdef          // Only the leading tab is manipulatedˇ»
 4432        "}
 4433        .replace("·", "")
 4434        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4435    );
 4436}
 4437
 4438#[gpui::test]
 4439async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4440    init_test(cx, |settings| {
 4441        settings.defaults.tab_size = NonZeroU32::new(3)
 4442    });
 4443
 4444    let mut cx = EditorTestContext::new(cx).await;
 4445
 4446    // MULTI SELECTION
 4447    // Ln.1 "«" tests empty lines
 4448    // Ln.11 tests just leading whitespace
 4449    cx.set_state(indoc! {"
 4450        «
 4451        abˇ»ˇc                 // No indentation
 4452         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4453          abc  «             // 2 spaces (< 3 so dont convert)
 4454           abc              // 3 spaces (convert)
 4455             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4456        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4457        «\t abc              // Tab followed by space
 4458         \tabc              // Space followed by tab (should be consumed due to tab)
 4459        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4460           \tˇ»  «\t
 4461           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4462    "});
 4463    cx.update_editor(|e, window, cx| {
 4464        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4465    });
 4466    cx.assert_editor_state(indoc! {"
 4467        «
 4468        abc                 // No indentation
 4469         abc                // 1 space (< 3 so dont convert)
 4470          abc               // 2 spaces (< 3 so dont convert)
 4471        \tabc              // 3 spaces (convert)
 4472        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4473        \t\t\tabc           // Already tab indented
 4474        \t abc              // Tab followed by space
 4475        \tabc              // Space followed by tab (should be consumed due to tab)
 4476        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4477        \t\t\t
 4478        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4479    "});
 4480
 4481    // Test on just a few lines, the other should remain unchanged
 4482    // Only lines (4, 8, 11, 12) should change
 4483    cx.set_state(
 4484        indoc! {"
 4485            ·
 4486            abc                 // No indentation
 4487             abc                // 1 space (< 3 so dont convert)
 4488              abc               // 2 spaces (< 3 so dont convert)
 4489            «   abc              // 3 spaces (convert)ˇ»
 4490                 abc            // 5 spaces (1 tab + 2 spaces)
 4491            \t\t\tabc           // Already tab indented
 4492            \t abc              // Tab followed by space
 4493             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4494               \t\t  \tabc      // Mixed indentation
 4495            \t \t  \t   \tabc   // Mixed indentation
 4496               \t  \tˇ
 4497            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4498        "}
 4499        .replace("·", "")
 4500        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4501    );
 4502    cx.update_editor(|e, window, cx| {
 4503        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4504    });
 4505    cx.assert_editor_state(
 4506        indoc! {"
 4507            ·
 4508            abc                 // No indentation
 4509             abc                // 1 space (< 3 so dont convert)
 4510              abc               // 2 spaces (< 3 so dont convert)
 4511            «\tabc              // 3 spaces (convert)ˇ»
 4512                 abc            // 5 spaces (1 tab + 2 spaces)
 4513            \t\t\tabc           // Already tab indented
 4514            \t abc              // Tab followed by space
 4515            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4516               \t\t  \tabc      // Mixed indentation
 4517            \t \t  \t   \tabc   // Mixed indentation
 4518            «\t\t\t
 4519            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4520        "}
 4521        .replace("·", "")
 4522        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4523    );
 4524
 4525    // SINGLE SELECTION
 4526    // Ln.1 "«" tests empty lines
 4527    // Ln.11 tests just leading whitespace
 4528    cx.set_state(indoc! {"
 4529        «
 4530        abc                 // No indentation
 4531         abc                // 1 space (< 3 so dont convert)
 4532          abc               // 2 spaces (< 3 so dont convert)
 4533           abc              // 3 spaces (convert)
 4534             abc            // 5 spaces (1 tab + 2 spaces)
 4535        \t\t\tabc           // Already tab indented
 4536        \t abc              // Tab followed by space
 4537         \tabc              // Space followed by tab (should be consumed due to tab)
 4538        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4539           \t  \t
 4540           abc   \t         // Only the leading spaces should be convertedˇ»
 4541    "});
 4542    cx.update_editor(|e, window, cx| {
 4543        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4544    });
 4545    cx.assert_editor_state(indoc! {"
 4546        «
 4547        abc                 // No indentation
 4548         abc                // 1 space (< 3 so dont convert)
 4549          abc               // 2 spaces (< 3 so dont convert)
 4550        \tabc              // 3 spaces (convert)
 4551        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4552        \t\t\tabc           // Already tab indented
 4553        \t abc              // Tab followed by space
 4554        \tabc              // Space followed by tab (should be consumed due to tab)
 4555        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4556        \t\t\t
 4557        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4558    "});
 4559}
 4560
 4561#[gpui::test]
 4562async fn test_toggle_case(cx: &mut TestAppContext) {
 4563    init_test(cx, |_| {});
 4564
 4565    let mut cx = EditorTestContext::new(cx).await;
 4566
 4567    // If all lower case -> upper case
 4568    cx.set_state(indoc! {"
 4569        «hello worldˇ»
 4570    "});
 4571    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4572    cx.assert_editor_state(indoc! {"
 4573        «HELLO WORLDˇ»
 4574    "});
 4575
 4576    // If all upper case -> lower case
 4577    cx.set_state(indoc! {"
 4578        «HELLO WORLDˇ»
 4579    "});
 4580    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4581    cx.assert_editor_state(indoc! {"
 4582        «hello worldˇ»
 4583    "});
 4584
 4585    // If any upper case characters are identified -> lower case
 4586    // This matches JetBrains IDEs
 4587    cx.set_state(indoc! {"
 4588        «hEllo worldˇ»
 4589    "});
 4590    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4591    cx.assert_editor_state(indoc! {"
 4592        «hello worldˇ»
 4593    "});
 4594}
 4595
 4596#[gpui::test]
 4597async fn test_manipulate_text(cx: &mut TestAppContext) {
 4598    init_test(cx, |_| {});
 4599
 4600    let mut cx = EditorTestContext::new(cx).await;
 4601
 4602    // Test convert_to_upper_case()
 4603    cx.set_state(indoc! {"
 4604        «hello worldˇ»
 4605    "});
 4606    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4607    cx.assert_editor_state(indoc! {"
 4608        «HELLO WORLDˇ»
 4609    "});
 4610
 4611    // Test convert_to_lower_case()
 4612    cx.set_state(indoc! {"
 4613        «HELLO WORLDˇ»
 4614    "});
 4615    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4616    cx.assert_editor_state(indoc! {"
 4617        «hello worldˇ»
 4618    "});
 4619
 4620    // Test multiple line, single selection case
 4621    cx.set_state(indoc! {"
 4622        «The quick brown
 4623        fox jumps over
 4624        the lazy dogˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4627    cx.assert_editor_state(indoc! {"
 4628        «The Quick Brown
 4629        Fox Jumps Over
 4630        The Lazy Dogˇ»
 4631    "});
 4632
 4633    // Test multiple line, single selection case
 4634    cx.set_state(indoc! {"
 4635        «The quick brown
 4636        fox jumps over
 4637        the lazy dogˇ»
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «TheQuickBrown
 4644        FoxJumpsOver
 4645        TheLazyDogˇ»
 4646    "});
 4647
 4648    // From here on out, test more complex cases of manipulate_text()
 4649
 4650    // Test no selection case - should affect words cursors are in
 4651    // Cursor at beginning, middle, and end of word
 4652    cx.set_state(indoc! {"
 4653        ˇhello big beauˇtiful worldˇ
 4654    "});
 4655    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4656    cx.assert_editor_state(indoc! {"
 4657        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4658    "});
 4659
 4660    // Test multiple selections on a single line and across multiple lines
 4661    cx.set_state(indoc! {"
 4662        «Theˇ» quick «brown
 4663        foxˇ» jumps «overˇ»
 4664        the «lazyˇ» dog
 4665    "});
 4666    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4667    cx.assert_editor_state(indoc! {"
 4668        «THEˇ» quick «BROWN
 4669        FOXˇ» jumps «OVERˇ»
 4670        the «LAZYˇ» dog
 4671    "});
 4672
 4673    // Test case where text length grows
 4674    cx.set_state(indoc! {"
 4675        «tschüߡ»
 4676    "});
 4677    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4678    cx.assert_editor_state(indoc! {"
 4679        «TSCHÜSSˇ»
 4680    "});
 4681
 4682    // Test to make sure we don't crash when text shrinks
 4683    cx.set_state(indoc! {"
 4684        aaa_bbbˇ
 4685    "});
 4686    cx.update_editor(|e, window, cx| {
 4687        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4688    });
 4689    cx.assert_editor_state(indoc! {"
 4690        «aaaBbbˇ»
 4691    "});
 4692
 4693    // Test to make sure we all aware of the fact that each word can grow and shrink
 4694    // Final selections should be aware of this fact
 4695    cx.set_state(indoc! {"
 4696        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4697    "});
 4698    cx.update_editor(|e, window, cx| {
 4699        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4700    });
 4701    cx.assert_editor_state(indoc! {"
 4702        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4703    "});
 4704
 4705    cx.set_state(indoc! {"
 4706        «hElLo, WoRld!ˇ»
 4707    "});
 4708    cx.update_editor(|e, window, cx| {
 4709        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4710    });
 4711    cx.assert_editor_state(indoc! {"
 4712        «HeLlO, wOrLD!ˇ»
 4713    "});
 4714}
 4715
 4716#[gpui::test]
 4717fn test_duplicate_line(cx: &mut TestAppContext) {
 4718    init_test(cx, |_| {});
 4719
 4720    let editor = cx.add_window(|window, cx| {
 4721        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4722        build_editor(buffer, window, cx)
 4723    });
 4724    _ = editor.update(cx, |editor, window, cx| {
 4725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4726            s.select_display_ranges([
 4727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4728                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4729                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4730                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4731            ])
 4732        });
 4733        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4734        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4735        assert_eq!(
 4736            editor.selections.display_ranges(cx),
 4737            vec![
 4738                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4740                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4741                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4742            ]
 4743        );
 4744    });
 4745
 4746    let editor = cx.add_window(|window, cx| {
 4747        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4748        build_editor(buffer, window, cx)
 4749    });
 4750    _ = editor.update(cx, |editor, window, cx| {
 4751        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4752            s.select_display_ranges([
 4753                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4754                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4755            ])
 4756        });
 4757        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4758        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4759        assert_eq!(
 4760            editor.selections.display_ranges(cx),
 4761            vec![
 4762                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4763                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4764            ]
 4765        );
 4766    });
 4767
 4768    // With `move_upwards` the selections stay in place, except for
 4769    // the lines inserted above them
 4770    let editor = cx.add_window(|window, cx| {
 4771        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4772        build_editor(buffer, window, cx)
 4773    });
 4774    _ = editor.update(cx, |editor, window, cx| {
 4775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4776            s.select_display_ranges([
 4777                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4778                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4779                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4780                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4781            ])
 4782        });
 4783        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4784        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4785        assert_eq!(
 4786            editor.selections.display_ranges(cx),
 4787            vec![
 4788                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4789                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4790                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4791                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4792            ]
 4793        );
 4794    });
 4795
 4796    let editor = cx.add_window(|window, cx| {
 4797        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4798        build_editor(buffer, window, cx)
 4799    });
 4800    _ = editor.update(cx, |editor, window, cx| {
 4801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4802            s.select_display_ranges([
 4803                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4804                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4805            ])
 4806        });
 4807        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4808        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4809        assert_eq!(
 4810            editor.selections.display_ranges(cx),
 4811            vec![
 4812                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4813                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4814            ]
 4815        );
 4816    });
 4817
 4818    let editor = cx.add_window(|window, cx| {
 4819        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4820        build_editor(buffer, window, cx)
 4821    });
 4822    _ = editor.update(cx, |editor, window, cx| {
 4823        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4824            s.select_display_ranges([
 4825                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4826                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4827            ])
 4828        });
 4829        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4830        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4831        assert_eq!(
 4832            editor.selections.display_ranges(cx),
 4833            vec![
 4834                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4835                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4836            ]
 4837        );
 4838    });
 4839}
 4840
 4841#[gpui::test]
 4842fn test_move_line_up_down(cx: &mut TestAppContext) {
 4843    init_test(cx, |_| {});
 4844
 4845    let editor = cx.add_window(|window, cx| {
 4846        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4847        build_editor(buffer, window, cx)
 4848    });
 4849    _ = editor.update(cx, |editor, window, cx| {
 4850        editor.fold_creases(
 4851            vec![
 4852                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4853                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4854                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4855            ],
 4856            true,
 4857            window,
 4858            cx,
 4859        );
 4860        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4861            s.select_display_ranges([
 4862                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4863                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4864                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4865                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4866            ])
 4867        });
 4868        assert_eq!(
 4869            editor.display_text(cx),
 4870            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4871        );
 4872
 4873        editor.move_line_up(&MoveLineUp, window, cx);
 4874        assert_eq!(
 4875            editor.display_text(cx),
 4876            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4877        );
 4878        assert_eq!(
 4879            editor.selections.display_ranges(cx),
 4880            vec![
 4881                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4882                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4883                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4884                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4885            ]
 4886        );
 4887    });
 4888
 4889    _ = editor.update(cx, |editor, window, cx| {
 4890        editor.move_line_down(&MoveLineDown, window, cx);
 4891        assert_eq!(
 4892            editor.display_text(cx),
 4893            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4894        );
 4895        assert_eq!(
 4896            editor.selections.display_ranges(cx),
 4897            vec![
 4898                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4899                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4900                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4901                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4902            ]
 4903        );
 4904    });
 4905
 4906    _ = editor.update(cx, |editor, window, cx| {
 4907        editor.move_line_down(&MoveLineDown, window, cx);
 4908        assert_eq!(
 4909            editor.display_text(cx),
 4910            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4911        );
 4912        assert_eq!(
 4913            editor.selections.display_ranges(cx),
 4914            vec![
 4915                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4916                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4917                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4918                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4919            ]
 4920        );
 4921    });
 4922
 4923    _ = editor.update(cx, |editor, window, cx| {
 4924        editor.move_line_up(&MoveLineUp, window, cx);
 4925        assert_eq!(
 4926            editor.display_text(cx),
 4927            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4928        );
 4929        assert_eq!(
 4930            editor.selections.display_ranges(cx),
 4931            vec![
 4932                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4933                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4934                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4935                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4936            ]
 4937        );
 4938    });
 4939}
 4940
 4941#[gpui::test]
 4942fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4943    init_test(cx, |_| {});
 4944
 4945    let editor = cx.add_window(|window, cx| {
 4946        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4947        build_editor(buffer, window, cx)
 4948    });
 4949    _ = editor.update(cx, |editor, window, cx| {
 4950        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4951        editor.insert_blocks(
 4952            [BlockProperties {
 4953                style: BlockStyle::Fixed,
 4954                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4955                height: Some(1),
 4956                render: Arc::new(|_| div().into_any()),
 4957                priority: 0,
 4958                render_in_minimap: true,
 4959            }],
 4960            Some(Autoscroll::fit()),
 4961            cx,
 4962        );
 4963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4964            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4965        });
 4966        editor.move_line_down(&MoveLineDown, window, cx);
 4967    });
 4968}
 4969
 4970#[gpui::test]
 4971async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4972    init_test(cx, |_| {});
 4973
 4974    let mut cx = EditorTestContext::new(cx).await;
 4975    cx.set_state(
 4976        &"
 4977            ˇzero
 4978            one
 4979            two
 4980            three
 4981            four
 4982            five
 4983        "
 4984        .unindent(),
 4985    );
 4986
 4987    // Create a four-line block that replaces three lines of text.
 4988    cx.update_editor(|editor, window, cx| {
 4989        let snapshot = editor.snapshot(window, cx);
 4990        let snapshot = &snapshot.buffer_snapshot;
 4991        let placement = BlockPlacement::Replace(
 4992            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4993        );
 4994        editor.insert_blocks(
 4995            [BlockProperties {
 4996                placement,
 4997                height: Some(4),
 4998                style: BlockStyle::Sticky,
 4999                render: Arc::new(|_| gpui::div().into_any_element()),
 5000                priority: 0,
 5001                render_in_minimap: true,
 5002            }],
 5003            None,
 5004            cx,
 5005        );
 5006    });
 5007
 5008    // Move down so that the cursor touches the block.
 5009    cx.update_editor(|editor, window, cx| {
 5010        editor.move_down(&Default::default(), window, cx);
 5011    });
 5012    cx.assert_editor_state(
 5013        &"
 5014            zero
 5015            «one
 5016            two
 5017            threeˇ»
 5018            four
 5019            five
 5020        "
 5021        .unindent(),
 5022    );
 5023
 5024    // Move down past the block.
 5025    cx.update_editor(|editor, window, cx| {
 5026        editor.move_down(&Default::default(), window, cx);
 5027    });
 5028    cx.assert_editor_state(
 5029        &"
 5030            zero
 5031            one
 5032            two
 5033            three
 5034            ˇfour
 5035            five
 5036        "
 5037        .unindent(),
 5038    );
 5039}
 5040
 5041#[gpui::test]
 5042fn test_transpose(cx: &mut TestAppContext) {
 5043    init_test(cx, |_| {});
 5044
 5045    _ = cx.add_window(|window, cx| {
 5046        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5047        editor.set_style(EditorStyle::default(), window, cx);
 5048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5049            s.select_ranges([1..1])
 5050        });
 5051        editor.transpose(&Default::default(), window, cx);
 5052        assert_eq!(editor.text(cx), "bac");
 5053        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5054
 5055        editor.transpose(&Default::default(), window, cx);
 5056        assert_eq!(editor.text(cx), "bca");
 5057        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5058
 5059        editor.transpose(&Default::default(), window, cx);
 5060        assert_eq!(editor.text(cx), "bac");
 5061        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5062
 5063        editor
 5064    });
 5065
 5066    _ = cx.add_window(|window, cx| {
 5067        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5068        editor.set_style(EditorStyle::default(), window, cx);
 5069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5070            s.select_ranges([3..3])
 5071        });
 5072        editor.transpose(&Default::default(), window, cx);
 5073        assert_eq!(editor.text(cx), "acb\nde");
 5074        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5075
 5076        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5077            s.select_ranges([4..4])
 5078        });
 5079        editor.transpose(&Default::default(), window, cx);
 5080        assert_eq!(editor.text(cx), "acbd\ne");
 5081        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5082
 5083        editor.transpose(&Default::default(), window, cx);
 5084        assert_eq!(editor.text(cx), "acbde\n");
 5085        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5086
 5087        editor.transpose(&Default::default(), window, cx);
 5088        assert_eq!(editor.text(cx), "acbd\ne");
 5089        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5090
 5091        editor
 5092    });
 5093
 5094    _ = cx.add_window(|window, cx| {
 5095        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5096        editor.set_style(EditorStyle::default(), window, cx);
 5097        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5098            s.select_ranges([1..1, 2..2, 4..4])
 5099        });
 5100        editor.transpose(&Default::default(), window, cx);
 5101        assert_eq!(editor.text(cx), "bacd\ne");
 5102        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5103
 5104        editor.transpose(&Default::default(), window, cx);
 5105        assert_eq!(editor.text(cx), "bcade\n");
 5106        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5107
 5108        editor.transpose(&Default::default(), window, cx);
 5109        assert_eq!(editor.text(cx), "bcda\ne");
 5110        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5111
 5112        editor.transpose(&Default::default(), window, cx);
 5113        assert_eq!(editor.text(cx), "bcade\n");
 5114        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5115
 5116        editor.transpose(&Default::default(), window, cx);
 5117        assert_eq!(editor.text(cx), "bcaed\n");
 5118        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5119
 5120        editor
 5121    });
 5122
 5123    _ = cx.add_window(|window, cx| {
 5124        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5125        editor.set_style(EditorStyle::default(), window, cx);
 5126        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5127            s.select_ranges([4..4])
 5128        });
 5129        editor.transpose(&Default::default(), window, cx);
 5130        assert_eq!(editor.text(cx), "🏀🍐✋");
 5131        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5132
 5133        editor.transpose(&Default::default(), window, cx);
 5134        assert_eq!(editor.text(cx), "🏀✋🍐");
 5135        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5136
 5137        editor.transpose(&Default::default(), window, cx);
 5138        assert_eq!(editor.text(cx), "🏀🍐✋");
 5139        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5140
 5141        editor
 5142    });
 5143}
 5144
 5145#[gpui::test]
 5146async fn test_rewrap(cx: &mut TestAppContext) {
 5147    init_test(cx, |settings| {
 5148        settings.languages.0.extend([
 5149            (
 5150                "Markdown".into(),
 5151                LanguageSettingsContent {
 5152                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5153                    preferred_line_length: Some(40),
 5154                    ..Default::default()
 5155                },
 5156            ),
 5157            (
 5158                "Plain Text".into(),
 5159                LanguageSettingsContent {
 5160                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5161                    preferred_line_length: Some(40),
 5162                    ..Default::default()
 5163                },
 5164            ),
 5165            (
 5166                "C++".into(),
 5167                LanguageSettingsContent {
 5168                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5169                    preferred_line_length: Some(40),
 5170                    ..Default::default()
 5171                },
 5172            ),
 5173            (
 5174                "Python".into(),
 5175                LanguageSettingsContent {
 5176                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5177                    preferred_line_length: Some(40),
 5178                    ..Default::default()
 5179                },
 5180            ),
 5181            (
 5182                "Rust".into(),
 5183                LanguageSettingsContent {
 5184                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5185                    preferred_line_length: Some(40),
 5186                    ..Default::default()
 5187                },
 5188            ),
 5189        ])
 5190    });
 5191
 5192    let mut cx = EditorTestContext::new(cx).await;
 5193
 5194    let cpp_language = Arc::new(Language::new(
 5195        LanguageConfig {
 5196            name: "C++".into(),
 5197            line_comments: vec!["// ".into()],
 5198            ..LanguageConfig::default()
 5199        },
 5200        None,
 5201    ));
 5202    let python_language = Arc::new(Language::new(
 5203        LanguageConfig {
 5204            name: "Python".into(),
 5205            line_comments: vec!["# ".into()],
 5206            ..LanguageConfig::default()
 5207        },
 5208        None,
 5209    ));
 5210    let markdown_language = Arc::new(Language::new(
 5211        LanguageConfig {
 5212            name: "Markdown".into(),
 5213            rewrap_prefixes: vec![
 5214                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5215                regex::Regex::new("[-*+]\\s+").unwrap(),
 5216            ],
 5217            ..LanguageConfig::default()
 5218        },
 5219        None,
 5220    ));
 5221    let rust_language = Arc::new(Language::new(
 5222        LanguageConfig {
 5223            name: "Rust".into(),
 5224            line_comments: vec!["// ".into(), "/// ".into()],
 5225            ..LanguageConfig::default()
 5226        },
 5227        Some(tree_sitter_rust::LANGUAGE.into()),
 5228    ));
 5229
 5230    let plaintext_language = Arc::new(Language::new(
 5231        LanguageConfig {
 5232            name: "Plain Text".into(),
 5233            ..LanguageConfig::default()
 5234        },
 5235        None,
 5236    ));
 5237
 5238    // Test basic rewrapping of a long line with a cursor
 5239    assert_rewrap(
 5240        indoc! {"
 5241            // ˇThis is a long comment that needs to be wrapped.
 5242        "},
 5243        indoc! {"
 5244            // ˇThis is a long comment that needs to
 5245            // be wrapped.
 5246        "},
 5247        cpp_language.clone(),
 5248        &mut cx,
 5249    );
 5250
 5251    // Test rewrapping a full selection
 5252    assert_rewrap(
 5253        indoc! {"
 5254            «// This selected long comment needs to be wrapped.ˇ»"
 5255        },
 5256        indoc! {"
 5257            «// This selected long comment needs to
 5258            // be wrapped.ˇ»"
 5259        },
 5260        cpp_language.clone(),
 5261        &mut cx,
 5262    );
 5263
 5264    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5265    assert_rewrap(
 5266        indoc! {"
 5267            // ˇThis is the first line.
 5268            // Thisˇ is the second line.
 5269            // This is the thirdˇ line, all part of one paragraph.
 5270         "},
 5271        indoc! {"
 5272            // ˇThis is the first line. Thisˇ is the
 5273            // second line. This is the thirdˇ line,
 5274            // all part of one paragraph.
 5275         "},
 5276        cpp_language.clone(),
 5277        &mut cx,
 5278    );
 5279
 5280    // Test multiple cursors in different paragraphs trigger separate rewraps
 5281    assert_rewrap(
 5282        indoc! {"
 5283            // ˇThis is the first paragraph, first line.
 5284            // ˇThis is the first paragraph, second line.
 5285
 5286            // ˇThis is the second paragraph, first line.
 5287            // ˇThis is the second paragraph, second line.
 5288        "},
 5289        indoc! {"
 5290            // ˇThis is the first paragraph, first
 5291            // line. ˇThis is the first paragraph,
 5292            // second line.
 5293
 5294            // ˇThis is the second paragraph, first
 5295            // line. ˇThis is the second paragraph,
 5296            // second line.
 5297        "},
 5298        cpp_language.clone(),
 5299        &mut cx,
 5300    );
 5301
 5302    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5303    assert_rewrap(
 5304        indoc! {"
 5305            «// A regular long long comment to be wrapped.
 5306            /// A documentation long comment to be wrapped.ˇ»
 5307          "},
 5308        indoc! {"
 5309            «// A regular long long comment to be
 5310            // wrapped.
 5311            /// A documentation long comment to be
 5312            /// wrapped.ˇ»
 5313          "},
 5314        rust_language.clone(),
 5315        &mut cx,
 5316    );
 5317
 5318    // Test that change in indentation level trigger seperate rewraps
 5319    assert_rewrap(
 5320        indoc! {"
 5321            fn foo() {
 5322                «// This is a long comment at the base indent.
 5323                    // This is a long comment at the next indent.ˇ»
 5324            }
 5325        "},
 5326        indoc! {"
 5327            fn foo() {
 5328                «// This is a long comment at the
 5329                // base indent.
 5330                    // This is a long comment at the
 5331                    // next indent.ˇ»
 5332            }
 5333        "},
 5334        rust_language.clone(),
 5335        &mut cx,
 5336    );
 5337
 5338    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5339    assert_rewrap(
 5340        indoc! {"
 5341            # ˇThis is a long comment using a pound sign.
 5342        "},
 5343        indoc! {"
 5344            # ˇThis is a long comment using a pound
 5345            # sign.
 5346        "},
 5347        python_language.clone(),
 5348        &mut cx,
 5349    );
 5350
 5351    // Test rewrapping only affects comments, not code even when selected
 5352    assert_rewrap(
 5353        indoc! {"
 5354            «/// This doc comment is long and should be wrapped.
 5355            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5356        "},
 5357        indoc! {"
 5358            «/// This doc comment is long and should
 5359            /// be wrapped.
 5360            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5361        "},
 5362        rust_language.clone(),
 5363        &mut cx,
 5364    );
 5365
 5366    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5367    assert_rewrap(
 5368        indoc! {"
 5369            # Header
 5370
 5371            A long long long line of markdown text to wrap.ˇ
 5372         "},
 5373        indoc! {"
 5374            # Header
 5375
 5376            A long long long line of markdown text
 5377            to wrap.ˇ
 5378         "},
 5379        markdown_language.clone(),
 5380        &mut cx,
 5381    );
 5382
 5383    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5384    assert_rewrap(
 5385        indoc! {"
 5386            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5387            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5388            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5389        "},
 5390        indoc! {"
 5391            «1. This is a numbered list item that is
 5392               very long and needs to be wrapped
 5393               properly.
 5394            2. This is a numbered list item that is
 5395               very long and needs to be wrapped
 5396               properly.
 5397            - This is an unordered list item that is
 5398              also very long and should not merge
 5399              with the numbered item.ˇ»
 5400        "},
 5401        markdown_language.clone(),
 5402        &mut cx,
 5403    );
 5404
 5405    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5406    assert_rewrap(
 5407        indoc! {"
 5408            «1. This is a numbered list item that is
 5409            very long and needs to be wrapped
 5410            properly.
 5411            2. This is a numbered list item that is
 5412            very long and needs to be wrapped
 5413            properly.
 5414            - This is an unordered list item that is
 5415            also very long and should not merge with
 5416            the numbered item.ˇ»
 5417        "},
 5418        indoc! {"
 5419            «1. This is a numbered list item that is
 5420               very long and needs to be wrapped
 5421               properly.
 5422            2. This is a numbered list item that is
 5423               very long and needs to be wrapped
 5424               properly.
 5425            - This is an unordered list item that is
 5426              also very long and should not merge
 5427              with the numbered item.ˇ»
 5428        "},
 5429        markdown_language.clone(),
 5430        &mut cx,
 5431    );
 5432
 5433    // Test that rewrapping maintain indents even when they already exists.
 5434    assert_rewrap(
 5435        indoc! {"
 5436            «1. This is a numbered list
 5437               item that is very long and needs to be wrapped properly.
 5438            2. This is a numbered list
 5439               item that is very long and needs to be wrapped properly.
 5440            - This is an unordered list item that is also very long and
 5441              should not merge with the numbered item.ˇ»
 5442        "},
 5443        indoc! {"
 5444            «1. This is a numbered list item that is
 5445               very long and needs to be wrapped
 5446               properly.
 5447            2. This is a numbered list item that is
 5448               very long and needs to be wrapped
 5449               properly.
 5450            - This is an unordered list item that is
 5451              also very long and should not merge
 5452              with the numbered item.ˇ»
 5453        "},
 5454        markdown_language.clone(),
 5455        &mut cx,
 5456    );
 5457
 5458    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5459    assert_rewrap(
 5460        indoc! {"
 5461            ˇThis is a very long line of plain text that will be wrapped.
 5462        "},
 5463        indoc! {"
 5464            ˇThis is a very long line of plain text
 5465            that will be wrapped.
 5466        "},
 5467        plaintext_language.clone(),
 5468        &mut cx,
 5469    );
 5470
 5471    // Test that non-commented code acts as a paragraph boundary within a selection
 5472    assert_rewrap(
 5473        indoc! {"
 5474               «// This is the first long comment block to be wrapped.
 5475               fn my_func(a: u32);
 5476               // This is the second long comment block to be wrapped.ˇ»
 5477           "},
 5478        indoc! {"
 5479               «// This is the first long comment block
 5480               // to be wrapped.
 5481               fn my_func(a: u32);
 5482               // This is the second long comment block
 5483               // to be wrapped.ˇ»
 5484           "},
 5485        rust_language.clone(),
 5486        &mut cx,
 5487    );
 5488
 5489    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5490    assert_rewrap(
 5491        indoc! {"
 5492            «ˇThis is a very long line that will be wrapped.
 5493
 5494            This is another paragraph in the same selection.»
 5495
 5496            «\tThis is a very long indented line that will be wrapped.ˇ»
 5497         "},
 5498        indoc! {"
 5499            «ˇThis is a very long line that will be
 5500            wrapped.
 5501
 5502            This is another paragraph in the same
 5503            selection.»
 5504
 5505            «\tThis is a very long indented line
 5506            \tthat will be wrapped.ˇ»
 5507         "},
 5508        plaintext_language.clone(),
 5509        &mut cx,
 5510    );
 5511
 5512    // Test that an empty comment line acts as a paragraph boundary
 5513    assert_rewrap(
 5514        indoc! {"
 5515            // ˇThis is a long comment that will be wrapped.
 5516            //
 5517            // And this is another long comment that will also be wrapped.ˇ
 5518         "},
 5519        indoc! {"
 5520            // ˇThis is a long comment that will be
 5521            // wrapped.
 5522            //
 5523            // And this is another long comment that
 5524            // will also be wrapped.ˇ
 5525         "},
 5526        cpp_language,
 5527        &mut cx,
 5528    );
 5529
 5530    #[track_caller]
 5531    fn assert_rewrap(
 5532        unwrapped_text: &str,
 5533        wrapped_text: &str,
 5534        language: Arc<Language>,
 5535        cx: &mut EditorTestContext,
 5536    ) {
 5537        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5538        cx.set_state(unwrapped_text);
 5539        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5540        cx.assert_editor_state(wrapped_text);
 5541    }
 5542}
 5543
 5544#[gpui::test]
 5545async fn test_hard_wrap(cx: &mut TestAppContext) {
 5546    init_test(cx, |_| {});
 5547    let mut cx = EditorTestContext::new(cx).await;
 5548
 5549    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5550    cx.update_editor(|editor, _, cx| {
 5551        editor.set_hard_wrap(Some(14), cx);
 5552    });
 5553
 5554    cx.set_state(indoc!(
 5555        "
 5556        one two three ˇ
 5557        "
 5558    ));
 5559    cx.simulate_input("four");
 5560    cx.run_until_parked();
 5561
 5562    cx.assert_editor_state(indoc!(
 5563        "
 5564        one two three
 5565        fourˇ
 5566        "
 5567    ));
 5568
 5569    cx.update_editor(|editor, window, cx| {
 5570        editor.newline(&Default::default(), window, cx);
 5571    });
 5572    cx.run_until_parked();
 5573    cx.assert_editor_state(indoc!(
 5574        "
 5575        one two three
 5576        four
 5577        ˇ
 5578        "
 5579    ));
 5580
 5581    cx.simulate_input("five");
 5582    cx.run_until_parked();
 5583    cx.assert_editor_state(indoc!(
 5584        "
 5585        one two three
 5586        four
 5587        fiveˇ
 5588        "
 5589    ));
 5590
 5591    cx.update_editor(|editor, window, cx| {
 5592        editor.newline(&Default::default(), window, cx);
 5593    });
 5594    cx.run_until_parked();
 5595    cx.simulate_input("# ");
 5596    cx.run_until_parked();
 5597    cx.assert_editor_state(indoc!(
 5598        "
 5599        one two three
 5600        four
 5601        five
 5602        # ˇ
 5603        "
 5604    ));
 5605
 5606    cx.update_editor(|editor, window, cx| {
 5607        editor.newline(&Default::default(), window, cx);
 5608    });
 5609    cx.run_until_parked();
 5610    cx.assert_editor_state(indoc!(
 5611        "
 5612        one two three
 5613        four
 5614        five
 5615        #\x20
 5616 5617        "
 5618    ));
 5619
 5620    cx.simulate_input(" 6");
 5621    cx.run_until_parked();
 5622    cx.assert_editor_state(indoc!(
 5623        "
 5624        one two three
 5625        four
 5626        five
 5627        #
 5628        # 6ˇ
 5629        "
 5630    ));
 5631}
 5632
 5633#[gpui::test]
 5634async fn test_clipboard(cx: &mut TestAppContext) {
 5635    init_test(cx, |_| {});
 5636
 5637    let mut cx = EditorTestContext::new(cx).await;
 5638
 5639    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5640    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5641    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5642
 5643    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5644    cx.set_state("two ˇfour ˇsix ˇ");
 5645    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5646    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5647
 5648    // Paste again but with only two cursors. Since the number of cursors doesn't
 5649    // match the number of slices in the clipboard, the entire clipboard text
 5650    // is pasted at each cursor.
 5651    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5652    cx.update_editor(|e, window, cx| {
 5653        e.handle_input("( ", window, cx);
 5654        e.paste(&Paste, window, cx);
 5655        e.handle_input(") ", window, cx);
 5656    });
 5657    cx.assert_editor_state(
 5658        &([
 5659            "( one✅ ",
 5660            "three ",
 5661            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5662            "three ",
 5663            "five ) ˇ",
 5664        ]
 5665        .join("\n")),
 5666    );
 5667
 5668    // Cut with three selections, one of which is full-line.
 5669    cx.set_state(indoc! {"
 5670        1«2ˇ»3
 5671        4ˇ567
 5672        «8ˇ»9"});
 5673    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5674    cx.assert_editor_state(indoc! {"
 5675        1ˇ3
 5676        ˇ9"});
 5677
 5678    // Paste with three selections, noticing how the copied selection that was full-line
 5679    // gets inserted before the second cursor.
 5680    cx.set_state(indoc! {"
 5681        1ˇ3
 5682 5683        «oˇ»ne"});
 5684    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5685    cx.assert_editor_state(indoc! {"
 5686        12ˇ3
 5687        4567
 5688 5689        8ˇne"});
 5690
 5691    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5692    cx.set_state(indoc! {"
 5693        The quick brown
 5694        fox juˇmps over
 5695        the lazy dog"});
 5696    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5697    assert_eq!(
 5698        cx.read_from_clipboard()
 5699            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5700        Some("fox jumps over\n".to_string())
 5701    );
 5702
 5703    // Paste with three selections, noticing how the copied full-line selection is inserted
 5704    // before the empty selections but replaces the selection that is non-empty.
 5705    cx.set_state(indoc! {"
 5706        Tˇhe quick brown
 5707        «foˇ»x jumps over
 5708        tˇhe lazy dog"});
 5709    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5710    cx.assert_editor_state(indoc! {"
 5711        fox jumps over
 5712        Tˇhe quick brown
 5713        fox jumps over
 5714        ˇx jumps over
 5715        fox jumps over
 5716        tˇhe lazy dog"});
 5717}
 5718
 5719#[gpui::test]
 5720async fn test_copy_trim(cx: &mut TestAppContext) {
 5721    init_test(cx, |_| {});
 5722
 5723    let mut cx = EditorTestContext::new(cx).await;
 5724    cx.set_state(
 5725        r#"            «for selection in selections.iter() {
 5726            let mut start = selection.start;
 5727            let mut end = selection.end;
 5728            let is_entire_line = selection.is_empty();
 5729            if is_entire_line {
 5730                start = Point::new(start.row, 0);ˇ»
 5731                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5732            }
 5733        "#,
 5734    );
 5735    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5736    assert_eq!(
 5737        cx.read_from_clipboard()
 5738            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5739        Some(
 5740            "for selection in selections.iter() {
 5741            let mut start = selection.start;
 5742            let mut end = selection.end;
 5743            let is_entire_line = selection.is_empty();
 5744            if is_entire_line {
 5745                start = Point::new(start.row, 0);"
 5746                .to_string()
 5747        ),
 5748        "Regular copying preserves all indentation selected",
 5749    );
 5750    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5751    assert_eq!(
 5752        cx.read_from_clipboard()
 5753            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5754        Some(
 5755            "for selection in selections.iter() {
 5756let mut start = selection.start;
 5757let mut end = selection.end;
 5758let is_entire_line = selection.is_empty();
 5759if is_entire_line {
 5760    start = Point::new(start.row, 0);"
 5761                .to_string()
 5762        ),
 5763        "Copying with stripping should strip all leading whitespaces"
 5764    );
 5765
 5766    cx.set_state(
 5767        r#"       «     for selection in selections.iter() {
 5768            let mut start = selection.start;
 5769            let mut end = selection.end;
 5770            let is_entire_line = selection.is_empty();
 5771            if is_entire_line {
 5772                start = Point::new(start.row, 0);ˇ»
 5773                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5774            }
 5775        "#,
 5776    );
 5777    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5778    assert_eq!(
 5779        cx.read_from_clipboard()
 5780            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5781        Some(
 5782            "     for selection in selections.iter() {
 5783            let mut start = selection.start;
 5784            let mut end = selection.end;
 5785            let is_entire_line = selection.is_empty();
 5786            if is_entire_line {
 5787                start = Point::new(start.row, 0);"
 5788                .to_string()
 5789        ),
 5790        "Regular copying preserves all indentation selected",
 5791    );
 5792    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5793    assert_eq!(
 5794        cx.read_from_clipboard()
 5795            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5796        Some(
 5797            "for selection in selections.iter() {
 5798let mut start = selection.start;
 5799let mut end = selection.end;
 5800let is_entire_line = selection.is_empty();
 5801if is_entire_line {
 5802    start = Point::new(start.row, 0);"
 5803                .to_string()
 5804        ),
 5805        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5806    );
 5807
 5808    cx.set_state(
 5809        r#"       «ˇ     for selection in selections.iter() {
 5810            let mut start = selection.start;
 5811            let mut end = selection.end;
 5812            let is_entire_line = selection.is_empty();
 5813            if is_entire_line {
 5814                start = Point::new(start.row, 0);»
 5815                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5816            }
 5817        "#,
 5818    );
 5819    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5820    assert_eq!(
 5821        cx.read_from_clipboard()
 5822            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5823        Some(
 5824            "     for selection in selections.iter() {
 5825            let mut start = selection.start;
 5826            let mut end = selection.end;
 5827            let is_entire_line = selection.is_empty();
 5828            if is_entire_line {
 5829                start = Point::new(start.row, 0);"
 5830                .to_string()
 5831        ),
 5832        "Regular copying for reverse selection works the same",
 5833    );
 5834    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5835    assert_eq!(
 5836        cx.read_from_clipboard()
 5837            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5838        Some(
 5839            "for selection in selections.iter() {
 5840let mut start = selection.start;
 5841let mut end = selection.end;
 5842let is_entire_line = selection.is_empty();
 5843if is_entire_line {
 5844    start = Point::new(start.row, 0);"
 5845                .to_string()
 5846        ),
 5847        "Copying with stripping for reverse selection works the same"
 5848    );
 5849
 5850    cx.set_state(
 5851        r#"            for selection «in selections.iter() {
 5852            let mut start = selection.start;
 5853            let mut end = selection.end;
 5854            let is_entire_line = selection.is_empty();
 5855            if is_entire_line {
 5856                start = Point::new(start.row, 0);ˇ»
 5857                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5858            }
 5859        "#,
 5860    );
 5861    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5862    assert_eq!(
 5863        cx.read_from_clipboard()
 5864            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5865        Some(
 5866            "in selections.iter() {
 5867            let mut start = selection.start;
 5868            let mut end = selection.end;
 5869            let is_entire_line = selection.is_empty();
 5870            if is_entire_line {
 5871                start = Point::new(start.row, 0);"
 5872                .to_string()
 5873        ),
 5874        "When selecting past the indent, the copying works as usual",
 5875    );
 5876    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5877    assert_eq!(
 5878        cx.read_from_clipboard()
 5879            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5880        Some(
 5881            "in selections.iter() {
 5882            let mut start = selection.start;
 5883            let mut end = selection.end;
 5884            let is_entire_line = selection.is_empty();
 5885            if is_entire_line {
 5886                start = Point::new(start.row, 0);"
 5887                .to_string()
 5888        ),
 5889        "When selecting past the indent, nothing is trimmed"
 5890    );
 5891
 5892    cx.set_state(
 5893        r#"            «for selection in selections.iter() {
 5894            let mut start = selection.start;
 5895
 5896            let mut end = selection.end;
 5897            let is_entire_line = selection.is_empty();
 5898            if is_entire_line {
 5899                start = Point::new(start.row, 0);
 5900ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5901            }
 5902        "#,
 5903    );
 5904    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5905    assert_eq!(
 5906        cx.read_from_clipboard()
 5907            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5908        Some(
 5909            "for selection in selections.iter() {
 5910let mut start = selection.start;
 5911
 5912let mut end = selection.end;
 5913let is_entire_line = selection.is_empty();
 5914if is_entire_line {
 5915    start = Point::new(start.row, 0);
 5916"
 5917            .to_string()
 5918        ),
 5919        "Copying with stripping should ignore empty lines"
 5920    );
 5921}
 5922
 5923#[gpui::test]
 5924async fn test_paste_multiline(cx: &mut TestAppContext) {
 5925    init_test(cx, |_| {});
 5926
 5927    let mut cx = EditorTestContext::new(cx).await;
 5928    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5929
 5930    // Cut an indented block, without the leading whitespace.
 5931    cx.set_state(indoc! {"
 5932        const a: B = (
 5933            c(),
 5934            «d(
 5935                e,
 5936                f
 5937            )ˇ»
 5938        );
 5939    "});
 5940    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5941    cx.assert_editor_state(indoc! {"
 5942        const a: B = (
 5943            c(),
 5944            ˇ
 5945        );
 5946    "});
 5947
 5948    // Paste it at the same position.
 5949    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5950    cx.assert_editor_state(indoc! {"
 5951        const a: B = (
 5952            c(),
 5953            d(
 5954                e,
 5955                f
 5956 5957        );
 5958    "});
 5959
 5960    // Paste it at a line with a lower indent level.
 5961    cx.set_state(indoc! {"
 5962        ˇ
 5963        const a: B = (
 5964            c(),
 5965        );
 5966    "});
 5967    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5968    cx.assert_editor_state(indoc! {"
 5969        d(
 5970            e,
 5971            f
 5972 5973        const a: B = (
 5974            c(),
 5975        );
 5976    "});
 5977
 5978    // Cut an indented block, with the leading whitespace.
 5979    cx.set_state(indoc! {"
 5980        const a: B = (
 5981            c(),
 5982        «    d(
 5983                e,
 5984                f
 5985            )
 5986        ˇ»);
 5987    "});
 5988    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5989    cx.assert_editor_state(indoc! {"
 5990        const a: B = (
 5991            c(),
 5992        ˇ);
 5993    "});
 5994
 5995    // Paste it at the same position.
 5996    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5997    cx.assert_editor_state(indoc! {"
 5998        const a: B = (
 5999            c(),
 6000            d(
 6001                e,
 6002                f
 6003            )
 6004        ˇ);
 6005    "});
 6006
 6007    // Paste it at a line with a higher indent level.
 6008    cx.set_state(indoc! {"
 6009        const a: B = (
 6010            c(),
 6011            d(
 6012                e,
 6013 6014            )
 6015        );
 6016    "});
 6017    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6018    cx.assert_editor_state(indoc! {"
 6019        const a: B = (
 6020            c(),
 6021            d(
 6022                e,
 6023                f    d(
 6024                    e,
 6025                    f
 6026                )
 6027        ˇ
 6028            )
 6029        );
 6030    "});
 6031
 6032    // Copy an indented block, starting mid-line
 6033    cx.set_state(indoc! {"
 6034        const a: B = (
 6035            c(),
 6036            somethin«g(
 6037                e,
 6038                f
 6039            )ˇ»
 6040        );
 6041    "});
 6042    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6043
 6044    // Paste it on a line with a lower indent level
 6045    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6046    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6047    cx.assert_editor_state(indoc! {"
 6048        const a: B = (
 6049            c(),
 6050            something(
 6051                e,
 6052                f
 6053            )
 6054        );
 6055        g(
 6056            e,
 6057            f
 6058"});
 6059}
 6060
 6061#[gpui::test]
 6062async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6063    init_test(cx, |_| {});
 6064
 6065    cx.write_to_clipboard(ClipboardItem::new_string(
 6066        "    d(\n        e\n    );\n".into(),
 6067    ));
 6068
 6069    let mut cx = EditorTestContext::new(cx).await;
 6070    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6071
 6072    cx.set_state(indoc! {"
 6073        fn a() {
 6074            b();
 6075            if c() {
 6076                ˇ
 6077            }
 6078        }
 6079    "});
 6080
 6081    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6082    cx.assert_editor_state(indoc! {"
 6083        fn a() {
 6084            b();
 6085            if c() {
 6086                d(
 6087                    e
 6088                );
 6089        ˇ
 6090            }
 6091        }
 6092    "});
 6093
 6094    cx.set_state(indoc! {"
 6095        fn a() {
 6096            b();
 6097            ˇ
 6098        }
 6099    "});
 6100
 6101    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6102    cx.assert_editor_state(indoc! {"
 6103        fn a() {
 6104            b();
 6105            d(
 6106                e
 6107            );
 6108        ˇ
 6109        }
 6110    "});
 6111}
 6112
 6113#[gpui::test]
 6114fn test_select_all(cx: &mut TestAppContext) {
 6115    init_test(cx, |_| {});
 6116
 6117    let editor = cx.add_window(|window, cx| {
 6118        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6119        build_editor(buffer, window, cx)
 6120    });
 6121    _ = editor.update(cx, |editor, window, cx| {
 6122        editor.select_all(&SelectAll, window, cx);
 6123        assert_eq!(
 6124            editor.selections.display_ranges(cx),
 6125            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6126        );
 6127    });
 6128}
 6129
 6130#[gpui::test]
 6131fn test_select_line(cx: &mut TestAppContext) {
 6132    init_test(cx, |_| {});
 6133
 6134    let editor = cx.add_window(|window, cx| {
 6135        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6136        build_editor(buffer, window, cx)
 6137    });
 6138    _ = editor.update(cx, |editor, window, cx| {
 6139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6140            s.select_display_ranges([
 6141                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6142                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6143                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6144                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6145            ])
 6146        });
 6147        editor.select_line(&SelectLine, window, cx);
 6148        assert_eq!(
 6149            editor.selections.display_ranges(cx),
 6150            vec![
 6151                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6152                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6153            ]
 6154        );
 6155    });
 6156
 6157    _ = editor.update(cx, |editor, window, cx| {
 6158        editor.select_line(&SelectLine, window, cx);
 6159        assert_eq!(
 6160            editor.selections.display_ranges(cx),
 6161            vec![
 6162                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6163                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6164            ]
 6165        );
 6166    });
 6167
 6168    _ = editor.update(cx, |editor, window, cx| {
 6169        editor.select_line(&SelectLine, window, cx);
 6170        assert_eq!(
 6171            editor.selections.display_ranges(cx),
 6172            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6173        );
 6174    });
 6175}
 6176
 6177#[gpui::test]
 6178async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6179    init_test(cx, |_| {});
 6180    let mut cx = EditorTestContext::new(cx).await;
 6181
 6182    #[track_caller]
 6183    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6184        cx.set_state(initial_state);
 6185        cx.update_editor(|e, window, cx| {
 6186            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 6187        });
 6188        cx.assert_editor_state(expected_state);
 6189    }
 6190
 6191    // Selection starts and ends at the middle of lines, left-to-right
 6192    test(
 6193        &mut cx,
 6194        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6195        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6196    );
 6197    // Same thing, right-to-left
 6198    test(
 6199        &mut cx,
 6200        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6201        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6202    );
 6203
 6204    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6205    test(
 6206        &mut cx,
 6207        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6208        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6209    );
 6210    // Same thing, right-to-left
 6211    test(
 6212        &mut cx,
 6213        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6214        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6215    );
 6216
 6217    // Whole buffer, left-to-right, last line ends with newline
 6218    test(
 6219        &mut cx,
 6220        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6221        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6222    );
 6223    // Same thing, right-to-left
 6224    test(
 6225        &mut cx,
 6226        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6227        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6228    );
 6229
 6230    // Starts at the end of a line, ends at the start of another
 6231    test(
 6232        &mut cx,
 6233        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6234        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6235    );
 6236}
 6237
 6238#[gpui::test]
 6239async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6240    init_test(cx, |_| {});
 6241
 6242    let editor = cx.add_window(|window, cx| {
 6243        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6244        build_editor(buffer, window, cx)
 6245    });
 6246
 6247    // setup
 6248    _ = editor.update(cx, |editor, window, cx| {
 6249        editor.fold_creases(
 6250            vec![
 6251                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6252                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6253                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6254            ],
 6255            true,
 6256            window,
 6257            cx,
 6258        );
 6259        assert_eq!(
 6260            editor.display_text(cx),
 6261            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6262        );
 6263    });
 6264
 6265    _ = editor.update(cx, |editor, window, cx| {
 6266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6267            s.select_display_ranges([
 6268                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6269                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6270                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6271                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6272            ])
 6273        });
 6274        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6275        assert_eq!(
 6276            editor.display_text(cx),
 6277            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6278        );
 6279    });
 6280    EditorTestContext::for_editor(editor, cx)
 6281        .await
 6282        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6283
 6284    _ = editor.update(cx, |editor, window, cx| {
 6285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6286            s.select_display_ranges([
 6287                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6288            ])
 6289        });
 6290        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 6291        assert_eq!(
 6292            editor.display_text(cx),
 6293            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6294        );
 6295        assert_eq!(
 6296            editor.selections.display_ranges(cx),
 6297            [
 6298                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6299                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6300                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6301                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6302                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6303                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6304                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6305            ]
 6306        );
 6307    });
 6308    EditorTestContext::for_editor(editor, cx)
 6309        .await
 6310        .assert_editor_state(
 6311            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6312        );
 6313}
 6314
 6315#[gpui::test]
 6316async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6317    init_test(cx, |_| {});
 6318
 6319    let mut cx = EditorTestContext::new(cx).await;
 6320
 6321    cx.set_state(indoc!(
 6322        r#"abc
 6323           defˇghi
 6324
 6325           jk
 6326           nlmo
 6327           "#
 6328    ));
 6329
 6330    cx.update_editor(|editor, window, cx| {
 6331        editor.add_selection_above(&Default::default(), window, cx);
 6332    });
 6333
 6334    cx.assert_editor_state(indoc!(
 6335        r#"abcˇ
 6336           defˇghi
 6337
 6338           jk
 6339           nlmo
 6340           "#
 6341    ));
 6342
 6343    cx.update_editor(|editor, window, cx| {
 6344        editor.add_selection_above(&Default::default(), window, cx);
 6345    });
 6346
 6347    cx.assert_editor_state(indoc!(
 6348        r#"abcˇ
 6349            defˇghi
 6350
 6351            jk
 6352            nlmo
 6353            "#
 6354    ));
 6355
 6356    cx.update_editor(|editor, window, cx| {
 6357        editor.add_selection_below(&Default::default(), window, cx);
 6358    });
 6359
 6360    cx.assert_editor_state(indoc!(
 6361        r#"abc
 6362           defˇghi
 6363
 6364           jk
 6365           nlmo
 6366           "#
 6367    ));
 6368
 6369    cx.update_editor(|editor, window, cx| {
 6370        editor.undo_selection(&Default::default(), window, cx);
 6371    });
 6372
 6373    cx.assert_editor_state(indoc!(
 6374        r#"abcˇ
 6375           defˇghi
 6376
 6377           jk
 6378           nlmo
 6379           "#
 6380    ));
 6381
 6382    cx.update_editor(|editor, window, cx| {
 6383        editor.redo_selection(&Default::default(), window, cx);
 6384    });
 6385
 6386    cx.assert_editor_state(indoc!(
 6387        r#"abc
 6388           defˇghi
 6389
 6390           jk
 6391           nlmo
 6392           "#
 6393    ));
 6394
 6395    cx.update_editor(|editor, window, cx| {
 6396        editor.add_selection_below(&Default::default(), window, cx);
 6397    });
 6398
 6399    cx.assert_editor_state(indoc!(
 6400        r#"abc
 6401           defˇghi
 6402           ˇ
 6403           jk
 6404           nlmo
 6405           "#
 6406    ));
 6407
 6408    cx.update_editor(|editor, window, cx| {
 6409        editor.add_selection_below(&Default::default(), window, cx);
 6410    });
 6411
 6412    cx.assert_editor_state(indoc!(
 6413        r#"abc
 6414           defˇghi
 6415           ˇ
 6416           jkˇ
 6417           nlmo
 6418           "#
 6419    ));
 6420
 6421    cx.update_editor(|editor, window, cx| {
 6422        editor.add_selection_below(&Default::default(), window, cx);
 6423    });
 6424
 6425    cx.assert_editor_state(indoc!(
 6426        r#"abc
 6427           defˇghi
 6428           ˇ
 6429           jkˇ
 6430           nlmˇo
 6431           "#
 6432    ));
 6433
 6434    cx.update_editor(|editor, window, cx| {
 6435        editor.add_selection_below(&Default::default(), window, cx);
 6436    });
 6437
 6438    cx.assert_editor_state(indoc!(
 6439        r#"abc
 6440           defˇghi
 6441           ˇ
 6442           jkˇ
 6443           nlmˇo
 6444           ˇ"#
 6445    ));
 6446
 6447    // change selections
 6448    cx.set_state(indoc!(
 6449        r#"abc
 6450           def«ˇg»hi
 6451
 6452           jk
 6453           nlmo
 6454           "#
 6455    ));
 6456
 6457    cx.update_editor(|editor, window, cx| {
 6458        editor.add_selection_below(&Default::default(), window, cx);
 6459    });
 6460
 6461    cx.assert_editor_state(indoc!(
 6462        r#"abc
 6463           def«ˇg»hi
 6464
 6465           jk
 6466           nlm«ˇo»
 6467           "#
 6468    ));
 6469
 6470    cx.update_editor(|editor, window, cx| {
 6471        editor.add_selection_below(&Default::default(), window, cx);
 6472    });
 6473
 6474    cx.assert_editor_state(indoc!(
 6475        r#"abc
 6476           def«ˇg»hi
 6477
 6478           jk
 6479           nlm«ˇo»
 6480           "#
 6481    ));
 6482
 6483    cx.update_editor(|editor, window, cx| {
 6484        editor.add_selection_above(&Default::default(), window, cx);
 6485    });
 6486
 6487    cx.assert_editor_state(indoc!(
 6488        r#"abc
 6489           def«ˇg»hi
 6490
 6491           jk
 6492           nlmo
 6493           "#
 6494    ));
 6495
 6496    cx.update_editor(|editor, window, cx| {
 6497        editor.add_selection_above(&Default::default(), window, cx);
 6498    });
 6499
 6500    cx.assert_editor_state(indoc!(
 6501        r#"abc
 6502           def«ˇg»hi
 6503
 6504           jk
 6505           nlmo
 6506           "#
 6507    ));
 6508
 6509    // Change selections again
 6510    cx.set_state(indoc!(
 6511        r#"a«bc
 6512           defgˇ»hi
 6513
 6514           jk
 6515           nlmo
 6516           "#
 6517    ));
 6518
 6519    cx.update_editor(|editor, window, cx| {
 6520        editor.add_selection_below(&Default::default(), window, cx);
 6521    });
 6522
 6523    cx.assert_editor_state(indoc!(
 6524        r#"a«bcˇ»
 6525           d«efgˇ»hi
 6526
 6527           j«kˇ»
 6528           nlmo
 6529           "#
 6530    ));
 6531
 6532    cx.update_editor(|editor, window, cx| {
 6533        editor.add_selection_below(&Default::default(), window, cx);
 6534    });
 6535    cx.assert_editor_state(indoc!(
 6536        r#"a«bcˇ»
 6537           d«efgˇ»hi
 6538
 6539           j«kˇ»
 6540           n«lmoˇ»
 6541           "#
 6542    ));
 6543    cx.update_editor(|editor, window, cx| {
 6544        editor.add_selection_above(&Default::default(), window, cx);
 6545    });
 6546
 6547    cx.assert_editor_state(indoc!(
 6548        r#"a«bcˇ»
 6549           d«efgˇ»hi
 6550
 6551           j«kˇ»
 6552           nlmo
 6553           "#
 6554    ));
 6555
 6556    // Change selections again
 6557    cx.set_state(indoc!(
 6558        r#"abc
 6559           d«ˇefghi
 6560
 6561           jk
 6562           nlm»o
 6563           "#
 6564    ));
 6565
 6566    cx.update_editor(|editor, window, cx| {
 6567        editor.add_selection_above(&Default::default(), window, cx);
 6568    });
 6569
 6570    cx.assert_editor_state(indoc!(
 6571        r#"a«ˇbc»
 6572           d«ˇef»ghi
 6573
 6574           j«ˇk»
 6575           n«ˇlm»o
 6576           "#
 6577    ));
 6578
 6579    cx.update_editor(|editor, window, cx| {
 6580        editor.add_selection_below(&Default::default(), window, cx);
 6581    });
 6582
 6583    cx.assert_editor_state(indoc!(
 6584        r#"abc
 6585           d«ˇef»ghi
 6586
 6587           j«ˇk»
 6588           n«ˇlm»o
 6589           "#
 6590    ));
 6591}
 6592
 6593#[gpui::test]
 6594async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6595    init_test(cx, |_| {});
 6596    let mut cx = EditorTestContext::new(cx).await;
 6597
 6598    cx.set_state(indoc!(
 6599        r#"line onˇe
 6600           liˇne two
 6601           line three
 6602           line four"#
 6603    ));
 6604
 6605    cx.update_editor(|editor, window, cx| {
 6606        editor.add_selection_below(&Default::default(), window, cx);
 6607    });
 6608
 6609    // test multiple cursors expand in the same direction
 6610    cx.assert_editor_state(indoc!(
 6611        r#"line onˇe
 6612           liˇne twˇo
 6613           liˇne three
 6614           line four"#
 6615    ));
 6616
 6617    cx.update_editor(|editor, window, cx| {
 6618        editor.add_selection_below(&Default::default(), window, cx);
 6619    });
 6620
 6621    cx.update_editor(|editor, window, cx| {
 6622        editor.add_selection_below(&Default::default(), window, cx);
 6623    });
 6624
 6625    // test multiple cursors expand below overflow
 6626    cx.assert_editor_state(indoc!(
 6627        r#"line onˇe
 6628           liˇne twˇo
 6629           liˇne thˇree
 6630           liˇne foˇur"#
 6631    ));
 6632
 6633    cx.update_editor(|editor, window, cx| {
 6634        editor.add_selection_above(&Default::default(), window, cx);
 6635    });
 6636
 6637    // test multiple cursors retrieves back correctly
 6638    cx.assert_editor_state(indoc!(
 6639        r#"line onˇe
 6640           liˇne twˇo
 6641           liˇne thˇree
 6642           line four"#
 6643    ));
 6644
 6645    cx.update_editor(|editor, window, cx| {
 6646        editor.add_selection_above(&Default::default(), window, cx);
 6647    });
 6648
 6649    cx.update_editor(|editor, window, cx| {
 6650        editor.add_selection_above(&Default::default(), window, cx);
 6651    });
 6652
 6653    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6654    cx.assert_editor_state(indoc!(
 6655        r#"liˇne onˇe
 6656           liˇne two
 6657           line three
 6658           line four"#
 6659    ));
 6660
 6661    cx.update_editor(|editor, window, cx| {
 6662        editor.undo_selection(&Default::default(), window, cx);
 6663    });
 6664
 6665    // test undo
 6666    cx.assert_editor_state(indoc!(
 6667        r#"line onˇe
 6668           liˇne twˇo
 6669           line three
 6670           line four"#
 6671    ));
 6672
 6673    cx.update_editor(|editor, window, cx| {
 6674        editor.redo_selection(&Default::default(), window, cx);
 6675    });
 6676
 6677    // test redo
 6678    cx.assert_editor_state(indoc!(
 6679        r#"liˇne onˇe
 6680           liˇne two
 6681           line three
 6682           line four"#
 6683    ));
 6684
 6685    cx.set_state(indoc!(
 6686        r#"abcd
 6687           ef«ghˇ»
 6688           ijkl
 6689           «mˇ»nop"#
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.add_selection_above(&Default::default(), window, cx);
 6694    });
 6695
 6696    // test multiple selections expand in the same direction
 6697    cx.assert_editor_state(indoc!(
 6698        r#"ab«cdˇ»
 6699           ef«ghˇ»
 6700           «iˇ»jkl
 6701           «mˇ»nop"#
 6702    ));
 6703
 6704    cx.update_editor(|editor, window, cx| {
 6705        editor.add_selection_above(&Default::default(), window, cx);
 6706    });
 6707
 6708    // test multiple selection upward overflow
 6709    cx.assert_editor_state(indoc!(
 6710        r#"ab«cdˇ»
 6711           «eˇ»f«ghˇ»
 6712           «iˇ»jkl
 6713           «mˇ»nop"#
 6714    ));
 6715
 6716    cx.update_editor(|editor, window, cx| {
 6717        editor.add_selection_below(&Default::default(), window, cx);
 6718    });
 6719
 6720    // test multiple selection retrieves back correctly
 6721    cx.assert_editor_state(indoc!(
 6722        r#"abcd
 6723           ef«ghˇ»
 6724           «iˇ»jkl
 6725           «mˇ»nop"#
 6726    ));
 6727
 6728    cx.update_editor(|editor, window, cx| {
 6729        editor.add_selection_below(&Default::default(), window, cx);
 6730    });
 6731
 6732    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6733    cx.assert_editor_state(indoc!(
 6734        r#"abcd
 6735           ef«ghˇ»
 6736           ij«klˇ»
 6737           «mˇ»nop"#
 6738    ));
 6739
 6740    cx.update_editor(|editor, window, cx| {
 6741        editor.undo_selection(&Default::default(), window, cx);
 6742    });
 6743
 6744    // test undo
 6745    cx.assert_editor_state(indoc!(
 6746        r#"abcd
 6747           ef«ghˇ»
 6748           «iˇ»jkl
 6749           «mˇ»nop"#
 6750    ));
 6751
 6752    cx.update_editor(|editor, window, cx| {
 6753        editor.redo_selection(&Default::default(), window, cx);
 6754    });
 6755
 6756    // test redo
 6757    cx.assert_editor_state(indoc!(
 6758        r#"abcd
 6759           ef«ghˇ»
 6760           ij«klˇ»
 6761           «mˇ»nop"#
 6762    ));
 6763}
 6764
 6765#[gpui::test]
 6766async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6767    init_test(cx, |_| {});
 6768    let mut cx = EditorTestContext::new(cx).await;
 6769
 6770    cx.set_state(indoc!(
 6771        r#"line onˇe
 6772           liˇne two
 6773           line three
 6774           line four"#
 6775    ));
 6776
 6777    cx.update_editor(|editor, window, cx| {
 6778        editor.add_selection_below(&Default::default(), window, cx);
 6779        editor.add_selection_below(&Default::default(), window, cx);
 6780        editor.add_selection_below(&Default::default(), window, cx);
 6781    });
 6782
 6783    // initial state with two multi cursor groups
 6784    cx.assert_editor_state(indoc!(
 6785        r#"line onˇe
 6786           liˇne twˇo
 6787           liˇne thˇree
 6788           liˇne foˇur"#
 6789    ));
 6790
 6791    // add single cursor in middle - simulate opt click
 6792    cx.update_editor(|editor, window, cx| {
 6793        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 6794        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6795        editor.end_selection(window, cx);
 6796    });
 6797
 6798    cx.assert_editor_state(indoc!(
 6799        r#"line onˇe
 6800           liˇne twˇo
 6801           liˇneˇ thˇree
 6802           liˇne foˇur"#
 6803    ));
 6804
 6805    cx.update_editor(|editor, window, cx| {
 6806        editor.add_selection_above(&Default::default(), window, cx);
 6807    });
 6808
 6809    // test new added selection expands above and existing selection shrinks
 6810    cx.assert_editor_state(indoc!(
 6811        r#"line onˇe
 6812           liˇneˇ twˇo
 6813           liˇneˇ thˇree
 6814           line four"#
 6815    ));
 6816
 6817    cx.update_editor(|editor, window, cx| {
 6818        editor.add_selection_above(&Default::default(), window, cx);
 6819    });
 6820
 6821    // test new added selection expands above and existing selection shrinks
 6822    cx.assert_editor_state(indoc!(
 6823        r#"lineˇ onˇe
 6824           liˇneˇ twˇo
 6825           lineˇ three
 6826           line four"#
 6827    ));
 6828
 6829    // intial state with two selection groups
 6830    cx.set_state(indoc!(
 6831        r#"abcd
 6832           ef«ghˇ»
 6833           ijkl
 6834           «mˇ»nop"#
 6835    ));
 6836
 6837    cx.update_editor(|editor, window, cx| {
 6838        editor.add_selection_above(&Default::default(), window, cx);
 6839        editor.add_selection_above(&Default::default(), window, cx);
 6840    });
 6841
 6842    cx.assert_editor_state(indoc!(
 6843        r#"ab«cdˇ»
 6844           «eˇ»f«ghˇ»
 6845           «iˇ»jkl
 6846           «mˇ»nop"#
 6847    ));
 6848
 6849    // add single selection in middle - simulate opt drag
 6850    cx.update_editor(|editor, window, cx| {
 6851        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 6852        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 6853        editor.update_selection(
 6854            DisplayPoint::new(DisplayRow(2), 4),
 6855            0,
 6856            gpui::Point::<f32>::default(),
 6857            window,
 6858            cx,
 6859        );
 6860        editor.end_selection(window, cx);
 6861    });
 6862
 6863    cx.assert_editor_state(indoc!(
 6864        r#"ab«cdˇ»
 6865           «eˇ»f«ghˇ»
 6866           «iˇ»jk«lˇ»
 6867           «mˇ»nop"#
 6868    ));
 6869
 6870    cx.update_editor(|editor, window, cx| {
 6871        editor.add_selection_below(&Default::default(), window, cx);
 6872    });
 6873
 6874    // test new added selection expands below, others shrinks from above
 6875    cx.assert_editor_state(indoc!(
 6876        r#"abcd
 6877           ef«ghˇ»
 6878           «iˇ»jk«lˇ»
 6879           «mˇ»no«pˇ»"#
 6880    ));
 6881}
 6882
 6883#[gpui::test]
 6884async fn test_select_next(cx: &mut TestAppContext) {
 6885    init_test(cx, |_| {});
 6886
 6887    let mut cx = EditorTestContext::new(cx).await;
 6888    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6889
 6890    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6891        .unwrap();
 6892    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6893
 6894    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6895        .unwrap();
 6896    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6897
 6898    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 6899    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 6900
 6901    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 6902    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 6903
 6904    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6905        .unwrap();
 6906    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6907
 6908    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6909        .unwrap();
 6910    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6911
 6912    // Test selection direction should be preserved
 6913    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 6914
 6915    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 6916        .unwrap();
 6917    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 6918}
 6919
 6920#[gpui::test]
 6921async fn test_select_all_matches(cx: &mut TestAppContext) {
 6922    init_test(cx, |_| {});
 6923
 6924    let mut cx = EditorTestContext::new(cx).await;
 6925
 6926    // Test caret-only selections
 6927    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 6928    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6929        .unwrap();
 6930    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 6931
 6932    // Test left-to-right selections
 6933    cx.set_state("abc\n«abcˇ»\nabc");
 6934    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6935        .unwrap();
 6936    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 6937
 6938    // Test right-to-left selections
 6939    cx.set_state("abc\n«ˇabc»\nabc");
 6940    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6941        .unwrap();
 6942    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 6943
 6944    // Test selecting whitespace with caret selection
 6945    cx.set_state("abc\nˇ   abc\nabc");
 6946    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6947        .unwrap();
 6948    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 6949
 6950    // Test selecting whitespace with left-to-right selection
 6951    cx.set_state("abc\n«ˇ  »abc\nabc");
 6952    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6953        .unwrap();
 6954    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 6955
 6956    // Test no matches with right-to-left selection
 6957    cx.set_state("abc\n«  ˇ»abc\nabc");
 6958    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6959        .unwrap();
 6960    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 6961
 6962    // Test with a single word and clip_at_line_ends=true (#29823)
 6963    cx.set_state("aˇbc");
 6964    cx.update_editor(|e, window, cx| {
 6965        e.set_clip_at_line_ends(true, cx);
 6966        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 6967        e.set_clip_at_line_ends(false, cx);
 6968    });
 6969    cx.assert_editor_state("«abcˇ»");
 6970}
 6971
 6972#[gpui::test]
 6973async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 6974    init_test(cx, |_| {});
 6975
 6976    let mut cx = EditorTestContext::new(cx).await;
 6977
 6978    let large_body_1 = "\nd".repeat(200);
 6979    let large_body_2 = "\ne".repeat(200);
 6980
 6981    cx.set_state(&format!(
 6982        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 6983    ));
 6984    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 6985        let scroll_position = editor.scroll_position(cx);
 6986        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 6987        scroll_position
 6988    });
 6989
 6990    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 6991        .unwrap();
 6992    cx.assert_editor_state(&format!(
 6993        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 6994    ));
 6995    let scroll_position_after_selection =
 6996        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 6997    assert_eq!(
 6998        initial_scroll_position, scroll_position_after_selection,
 6999        "Scroll position should not change after selecting all matches"
 7000    );
 7001}
 7002
 7003#[gpui::test]
 7004async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7005    init_test(cx, |_| {});
 7006
 7007    let mut cx = EditorLspTestContext::new_rust(
 7008        lsp::ServerCapabilities {
 7009            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7010            ..Default::default()
 7011        },
 7012        cx,
 7013    )
 7014    .await;
 7015
 7016    cx.set_state(indoc! {"
 7017        line 1
 7018        line 2
 7019        linˇe 3
 7020        line 4
 7021        line 5
 7022    "});
 7023
 7024    // Make an edit
 7025    cx.update_editor(|editor, window, cx| {
 7026        editor.handle_input("X", window, cx);
 7027    });
 7028
 7029    // Move cursor to a different position
 7030    cx.update_editor(|editor, window, cx| {
 7031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7032            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7033        });
 7034    });
 7035
 7036    cx.assert_editor_state(indoc! {"
 7037        line 1
 7038        line 2
 7039        linXe 3
 7040        line 4
 7041        liˇne 5
 7042    "});
 7043
 7044    cx.lsp
 7045        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7046            Ok(Some(vec![lsp::TextEdit::new(
 7047                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7048                "PREFIX ".to_string(),
 7049            )]))
 7050        });
 7051
 7052    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7053        .unwrap()
 7054        .await
 7055        .unwrap();
 7056
 7057    cx.assert_editor_state(indoc! {"
 7058        PREFIX line 1
 7059        line 2
 7060        linXe 3
 7061        line 4
 7062        liˇne 5
 7063    "});
 7064
 7065    // Undo formatting
 7066    cx.update_editor(|editor, window, cx| {
 7067        editor.undo(&Default::default(), window, cx);
 7068    });
 7069
 7070    // Verify cursor moved back to position after edit
 7071    cx.assert_editor_state(indoc! {"
 7072        line 1
 7073        line 2
 7074        linXˇe 3
 7075        line 4
 7076        line 5
 7077    "});
 7078}
 7079
 7080#[gpui::test]
 7081async fn test_undo_inline_completion_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7082    init_test(cx, |_| {});
 7083
 7084    let mut cx = EditorTestContext::new(cx).await;
 7085
 7086    let provider = cx.new(|_| FakeInlineCompletionProvider::default());
 7087    cx.update_editor(|editor, window, cx| {
 7088        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7089    });
 7090
 7091    cx.set_state(indoc! {"
 7092        line 1
 7093        line 2
 7094        linˇe 3
 7095        line 4
 7096        line 5
 7097        line 6
 7098        line 7
 7099        line 8
 7100        line 9
 7101        line 10
 7102    "});
 7103
 7104    let snapshot = cx.buffer_snapshot();
 7105    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7106
 7107    cx.update(|_, cx| {
 7108        provider.update(cx, |provider, _| {
 7109            provider.set_inline_completion(Some(inline_completion::InlineCompletion {
 7110                id: None,
 7111                edits: vec![(edit_position..edit_position, "X".into())],
 7112                edit_preview: None,
 7113            }))
 7114        })
 7115    });
 7116
 7117    cx.update_editor(|editor, window, cx| editor.update_visible_inline_completion(window, cx));
 7118    cx.update_editor(|editor, window, cx| {
 7119        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7120    });
 7121
 7122    cx.assert_editor_state(indoc! {"
 7123        line 1
 7124        line 2
 7125        lineXˇ 3
 7126        line 4
 7127        line 5
 7128        line 6
 7129        line 7
 7130        line 8
 7131        line 9
 7132        line 10
 7133    "});
 7134
 7135    cx.update_editor(|editor, window, cx| {
 7136        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7137            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7138        });
 7139    });
 7140
 7141    cx.assert_editor_state(indoc! {"
 7142        line 1
 7143        line 2
 7144        lineX 3
 7145        line 4
 7146        line 5
 7147        line 6
 7148        line 7
 7149        line 8
 7150        line 9
 7151        liˇne 10
 7152    "});
 7153
 7154    cx.update_editor(|editor, window, cx| {
 7155        editor.undo(&Default::default(), window, cx);
 7156    });
 7157
 7158    cx.assert_editor_state(indoc! {"
 7159        line 1
 7160        line 2
 7161        lineˇ 3
 7162        line 4
 7163        line 5
 7164        line 6
 7165        line 7
 7166        line 8
 7167        line 9
 7168        line 10
 7169    "});
 7170}
 7171
 7172#[gpui::test]
 7173async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7174    init_test(cx, |_| {});
 7175
 7176    let mut cx = EditorTestContext::new(cx).await;
 7177    cx.set_state(
 7178        r#"let foo = 2;
 7179lˇet foo = 2;
 7180let fooˇ = 2;
 7181let foo = 2;
 7182let foo = ˇ2;"#,
 7183    );
 7184
 7185    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7186        .unwrap();
 7187    cx.assert_editor_state(
 7188        r#"let foo = 2;
 7189«letˇ» foo = 2;
 7190let «fooˇ» = 2;
 7191let foo = 2;
 7192let foo = «2ˇ»;"#,
 7193    );
 7194
 7195    // noop for multiple selections with different contents
 7196    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7197        .unwrap();
 7198    cx.assert_editor_state(
 7199        r#"let foo = 2;
 7200«letˇ» foo = 2;
 7201let «fooˇ» = 2;
 7202let foo = 2;
 7203let foo = «2ˇ»;"#,
 7204    );
 7205
 7206    // Test last selection direction should be preserved
 7207    cx.set_state(
 7208        r#"let foo = 2;
 7209let foo = 2;
 7210let «fooˇ» = 2;
 7211let «ˇfoo» = 2;
 7212let foo = 2;"#,
 7213    );
 7214
 7215    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7216        .unwrap();
 7217    cx.assert_editor_state(
 7218        r#"let foo = 2;
 7219let foo = 2;
 7220let «fooˇ» = 2;
 7221let «ˇfoo» = 2;
 7222let «ˇfoo» = 2;"#,
 7223    );
 7224}
 7225
 7226#[gpui::test]
 7227async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7228    init_test(cx, |_| {});
 7229
 7230    let mut cx =
 7231        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7232
 7233    cx.assert_editor_state(indoc! {"
 7234        ˇbbb
 7235        ccc
 7236
 7237        bbb
 7238        ccc
 7239        "});
 7240    cx.dispatch_action(SelectPrevious::default());
 7241    cx.assert_editor_state(indoc! {"
 7242                «bbbˇ»
 7243                ccc
 7244
 7245                bbb
 7246                ccc
 7247                "});
 7248    cx.dispatch_action(SelectPrevious::default());
 7249    cx.assert_editor_state(indoc! {"
 7250                «bbbˇ»
 7251                ccc
 7252
 7253                «bbbˇ»
 7254                ccc
 7255                "});
 7256}
 7257
 7258#[gpui::test]
 7259async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7260    init_test(cx, |_| {});
 7261
 7262    let mut cx = EditorTestContext::new(cx).await;
 7263    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7264
 7265    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7266        .unwrap();
 7267    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7268
 7269    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7270        .unwrap();
 7271    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7272
 7273    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7274    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7275
 7276    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7277    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7278
 7279    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7280        .unwrap();
 7281    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7282
 7283    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7284        .unwrap();
 7285    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7286}
 7287
 7288#[gpui::test]
 7289async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7290    init_test(cx, |_| {});
 7291
 7292    let mut cx = EditorTestContext::new(cx).await;
 7293    cx.set_state("");
 7294
 7295    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7296        .unwrap();
 7297    cx.assert_editor_state("«aˇ»");
 7298    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7299        .unwrap();
 7300    cx.assert_editor_state("«aˇ»");
 7301}
 7302
 7303#[gpui::test]
 7304async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7305    init_test(cx, |_| {});
 7306
 7307    let mut cx = EditorTestContext::new(cx).await;
 7308    cx.set_state(
 7309        r#"let foo = 2;
 7310lˇet foo = 2;
 7311let fooˇ = 2;
 7312let foo = 2;
 7313let foo = ˇ2;"#,
 7314    );
 7315
 7316    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7317        .unwrap();
 7318    cx.assert_editor_state(
 7319        r#"let foo = 2;
 7320«letˇ» foo = 2;
 7321let «fooˇ» = 2;
 7322let foo = 2;
 7323let foo = «2ˇ»;"#,
 7324    );
 7325
 7326    // noop for multiple selections with different contents
 7327    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7328        .unwrap();
 7329    cx.assert_editor_state(
 7330        r#"let foo = 2;
 7331«letˇ» foo = 2;
 7332let «fooˇ» = 2;
 7333let foo = 2;
 7334let foo = «2ˇ»;"#,
 7335    );
 7336}
 7337
 7338#[gpui::test]
 7339async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7340    init_test(cx, |_| {});
 7341
 7342    let mut cx = EditorTestContext::new(cx).await;
 7343    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7344
 7345    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7346        .unwrap();
 7347    // selection direction is preserved
 7348    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7349
 7350    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7351        .unwrap();
 7352    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7353
 7354    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7355    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7356
 7357    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7358    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7359
 7360    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7361        .unwrap();
 7362    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7363
 7364    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7365        .unwrap();
 7366    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7367}
 7368
 7369#[gpui::test]
 7370async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7371    init_test(cx, |_| {});
 7372
 7373    let language = Arc::new(Language::new(
 7374        LanguageConfig::default(),
 7375        Some(tree_sitter_rust::LANGUAGE.into()),
 7376    ));
 7377
 7378    let text = r#"
 7379        use mod1::mod2::{mod3, mod4};
 7380
 7381        fn fn_1(param1: bool, param2: &str) {
 7382            let var1 = "text";
 7383        }
 7384    "#
 7385    .unindent();
 7386
 7387    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7388    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7389    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7390
 7391    editor
 7392        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7393        .await;
 7394
 7395    editor.update_in(cx, |editor, window, cx| {
 7396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7397            s.select_display_ranges([
 7398                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7399                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7400                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7401            ]);
 7402        });
 7403        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7404    });
 7405    editor.update(cx, |editor, cx| {
 7406        assert_text_with_selections(
 7407            editor,
 7408            indoc! {r#"
 7409                use mod1::mod2::{mod3, «mod4ˇ»};
 7410
 7411                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7412                    let var1 = "«ˇtext»";
 7413                }
 7414            "#},
 7415            cx,
 7416        );
 7417    });
 7418
 7419    editor.update_in(cx, |editor, window, cx| {
 7420        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7421    });
 7422    editor.update(cx, |editor, cx| {
 7423        assert_text_with_selections(
 7424            editor,
 7425            indoc! {r#"
 7426                use mod1::mod2::«{mod3, mod4}ˇ»;
 7427
 7428                «ˇfn fn_1(param1: bool, param2: &str) {
 7429                    let var1 = "text";
 7430 7431            "#},
 7432            cx,
 7433        );
 7434    });
 7435
 7436    editor.update_in(cx, |editor, window, cx| {
 7437        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7438    });
 7439    assert_eq!(
 7440        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7441        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7442    );
 7443
 7444    // Trying to expand the selected syntax node one more time has no effect.
 7445    editor.update_in(cx, |editor, window, cx| {
 7446        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7447    });
 7448    assert_eq!(
 7449        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7450        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7451    );
 7452
 7453    editor.update_in(cx, |editor, window, cx| {
 7454        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7455    });
 7456    editor.update(cx, |editor, cx| {
 7457        assert_text_with_selections(
 7458            editor,
 7459            indoc! {r#"
 7460                use mod1::mod2::«{mod3, mod4}ˇ»;
 7461
 7462                «ˇfn fn_1(param1: bool, param2: &str) {
 7463                    let var1 = "text";
 7464 7465            "#},
 7466            cx,
 7467        );
 7468    });
 7469
 7470    editor.update_in(cx, |editor, window, cx| {
 7471        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7472    });
 7473    editor.update(cx, |editor, cx| {
 7474        assert_text_with_selections(
 7475            editor,
 7476            indoc! {r#"
 7477                use mod1::mod2::{mod3, «mod4ˇ»};
 7478
 7479                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7480                    let var1 = "«ˇtext»";
 7481                }
 7482            "#},
 7483            cx,
 7484        );
 7485    });
 7486
 7487    editor.update_in(cx, |editor, window, cx| {
 7488        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7489    });
 7490    editor.update(cx, |editor, cx| {
 7491        assert_text_with_selections(
 7492            editor,
 7493            indoc! {r#"
 7494                use mod1::mod2::{mod3, mo«ˇ»d4};
 7495
 7496                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7497                    let var1 = "te«ˇ»xt";
 7498                }
 7499            "#},
 7500            cx,
 7501        );
 7502    });
 7503
 7504    // Trying to shrink the selected syntax node one more time has no effect.
 7505    editor.update_in(cx, |editor, window, cx| {
 7506        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7507    });
 7508    editor.update_in(cx, |editor, _, cx| {
 7509        assert_text_with_selections(
 7510            editor,
 7511            indoc! {r#"
 7512                use mod1::mod2::{mod3, mo«ˇ»d4};
 7513
 7514                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7515                    let var1 = "te«ˇ»xt";
 7516                }
 7517            "#},
 7518            cx,
 7519        );
 7520    });
 7521
 7522    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7523    // a fold.
 7524    editor.update_in(cx, |editor, window, cx| {
 7525        editor.fold_creases(
 7526            vec![
 7527                Crease::simple(
 7528                    Point::new(0, 21)..Point::new(0, 24),
 7529                    FoldPlaceholder::test(),
 7530                ),
 7531                Crease::simple(
 7532                    Point::new(3, 20)..Point::new(3, 22),
 7533                    FoldPlaceholder::test(),
 7534                ),
 7535            ],
 7536            true,
 7537            window,
 7538            cx,
 7539        );
 7540        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7541    });
 7542    editor.update(cx, |editor, cx| {
 7543        assert_text_with_selections(
 7544            editor,
 7545            indoc! {r#"
 7546                use mod1::mod2::«{mod3, mod4}ˇ»;
 7547
 7548                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7549                    let var1 = "«ˇtext»";
 7550                }
 7551            "#},
 7552            cx,
 7553        );
 7554    });
 7555}
 7556
 7557#[gpui::test]
 7558async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7559    init_test(cx, |_| {});
 7560
 7561    let language = Arc::new(Language::new(
 7562        LanguageConfig::default(),
 7563        Some(tree_sitter_rust::LANGUAGE.into()),
 7564    ));
 7565
 7566    let text = "let a = 2;";
 7567
 7568    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7569    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7570    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7571
 7572    editor
 7573        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7574        .await;
 7575
 7576    // Test case 1: Cursor at end of word
 7577    editor.update_in(cx, |editor, window, cx| {
 7578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7579            s.select_display_ranges([
 7580                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7581            ]);
 7582        });
 7583    });
 7584    editor.update(cx, |editor, cx| {
 7585        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7586    });
 7587    editor.update_in(cx, |editor, window, cx| {
 7588        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7589    });
 7590    editor.update(cx, |editor, cx| {
 7591        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7592    });
 7593    editor.update_in(cx, |editor, window, cx| {
 7594        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7595    });
 7596    editor.update(cx, |editor, cx| {
 7597        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7598    });
 7599
 7600    // Test case 2: Cursor at end of statement
 7601    editor.update_in(cx, |editor, window, cx| {
 7602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7603            s.select_display_ranges([
 7604                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7605            ]);
 7606        });
 7607    });
 7608    editor.update(cx, |editor, cx| {
 7609        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7610    });
 7611    editor.update_in(cx, |editor, window, cx| {
 7612        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7613    });
 7614    editor.update(cx, |editor, cx| {
 7615        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7616    });
 7617}
 7618
 7619#[gpui::test]
 7620async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7621    init_test(cx, |_| {});
 7622
 7623    let language = Arc::new(Language::new(
 7624        LanguageConfig::default(),
 7625        Some(tree_sitter_rust::LANGUAGE.into()),
 7626    ));
 7627
 7628    let text = r#"
 7629        use mod1::mod2::{mod3, mod4};
 7630
 7631        fn fn_1(param1: bool, param2: &str) {
 7632            let var1 = "hello world";
 7633        }
 7634    "#
 7635    .unindent();
 7636
 7637    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7639    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7640
 7641    editor
 7642        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7643        .await;
 7644
 7645    // Test 1: Cursor on a letter of a string word
 7646    editor.update_in(cx, |editor, window, cx| {
 7647        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7648            s.select_display_ranges([
 7649                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7650            ]);
 7651        });
 7652    });
 7653    editor.update_in(cx, |editor, window, cx| {
 7654        assert_text_with_selections(
 7655            editor,
 7656            indoc! {r#"
 7657                use mod1::mod2::{mod3, mod4};
 7658
 7659                fn fn_1(param1: bool, param2: &str) {
 7660                    let var1 = "hˇello world";
 7661                }
 7662            "#},
 7663            cx,
 7664        );
 7665        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7666        assert_text_with_selections(
 7667            editor,
 7668            indoc! {r#"
 7669                use mod1::mod2::{mod3, mod4};
 7670
 7671                fn fn_1(param1: bool, param2: &str) {
 7672                    let var1 = "«ˇhello» world";
 7673                }
 7674            "#},
 7675            cx,
 7676        );
 7677    });
 7678
 7679    // Test 2: Partial selection within a word
 7680    editor.update_in(cx, |editor, window, cx| {
 7681        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7682            s.select_display_ranges([
 7683                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7684            ]);
 7685        });
 7686    });
 7687    editor.update_in(cx, |editor, window, cx| {
 7688        assert_text_with_selections(
 7689            editor,
 7690            indoc! {r#"
 7691                use mod1::mod2::{mod3, mod4};
 7692
 7693                fn fn_1(param1: bool, param2: &str) {
 7694                    let var1 = "h«elˇ»lo world";
 7695                }
 7696            "#},
 7697            cx,
 7698        );
 7699        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7700        assert_text_with_selections(
 7701            editor,
 7702            indoc! {r#"
 7703                use mod1::mod2::{mod3, mod4};
 7704
 7705                fn fn_1(param1: bool, param2: &str) {
 7706                    let var1 = "«ˇhello» world";
 7707                }
 7708            "#},
 7709            cx,
 7710        );
 7711    });
 7712
 7713    // Test 3: Complete word already selected
 7714    editor.update_in(cx, |editor, window, cx| {
 7715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7716            s.select_display_ranges([
 7717                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7718            ]);
 7719        });
 7720    });
 7721    editor.update_in(cx, |editor, window, cx| {
 7722        assert_text_with_selections(
 7723            editor,
 7724            indoc! {r#"
 7725                use mod1::mod2::{mod3, mod4};
 7726
 7727                fn fn_1(param1: bool, param2: &str) {
 7728                    let var1 = "«helloˇ» world";
 7729                }
 7730            "#},
 7731            cx,
 7732        );
 7733        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7734        assert_text_with_selections(
 7735            editor,
 7736            indoc! {r#"
 7737                use mod1::mod2::{mod3, mod4};
 7738
 7739                fn fn_1(param1: bool, param2: &str) {
 7740                    let var1 = "«hello worldˇ»";
 7741                }
 7742            "#},
 7743            cx,
 7744        );
 7745    });
 7746
 7747    // Test 4: Selection spanning across words
 7748    editor.update_in(cx, |editor, window, cx| {
 7749        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7750            s.select_display_ranges([
 7751                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7752            ]);
 7753        });
 7754    });
 7755    editor.update_in(cx, |editor, window, cx| {
 7756        assert_text_with_selections(
 7757            editor,
 7758            indoc! {r#"
 7759                use mod1::mod2::{mod3, mod4};
 7760
 7761                fn fn_1(param1: bool, param2: &str) {
 7762                    let var1 = "hel«lo woˇ»rld";
 7763                }
 7764            "#},
 7765            cx,
 7766        );
 7767        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7768        assert_text_with_selections(
 7769            editor,
 7770            indoc! {r#"
 7771                use mod1::mod2::{mod3, mod4};
 7772
 7773                fn fn_1(param1: bool, param2: &str) {
 7774                    let var1 = "«ˇhello world»";
 7775                }
 7776            "#},
 7777            cx,
 7778        );
 7779    });
 7780
 7781    // Test 5: Expansion beyond string
 7782    editor.update_in(cx, |editor, window, cx| {
 7783        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7784        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7785        assert_text_with_selections(
 7786            editor,
 7787            indoc! {r#"
 7788                use mod1::mod2::{mod3, mod4};
 7789
 7790                fn fn_1(param1: bool, param2: &str) {
 7791                    «ˇlet var1 = "hello world";»
 7792                }
 7793            "#},
 7794            cx,
 7795        );
 7796    });
 7797}
 7798
 7799#[gpui::test]
 7800async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 7801    init_test(cx, |_| {});
 7802
 7803    let base_text = r#"
 7804        impl A {
 7805            // this is an uncommitted comment
 7806
 7807            fn b() {
 7808                c();
 7809            }
 7810
 7811            // this is another uncommitted comment
 7812
 7813            fn d() {
 7814                // e
 7815                // f
 7816            }
 7817        }
 7818
 7819        fn g() {
 7820            // h
 7821        }
 7822    "#
 7823    .unindent();
 7824
 7825    let text = r#"
 7826        ˇimpl A {
 7827
 7828            fn b() {
 7829                c();
 7830            }
 7831
 7832            fn d() {
 7833                // e
 7834                // f
 7835            }
 7836        }
 7837
 7838        fn g() {
 7839            // h
 7840        }
 7841    "#
 7842    .unindent();
 7843
 7844    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7845    cx.set_state(&text);
 7846    cx.set_head_text(&base_text);
 7847    cx.update_editor(|editor, window, cx| {
 7848        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 7849    });
 7850
 7851    cx.assert_state_with_diff(
 7852        "
 7853        ˇimpl A {
 7854      -     // this is an uncommitted comment
 7855
 7856            fn b() {
 7857                c();
 7858            }
 7859
 7860      -     // this is another uncommitted comment
 7861      -
 7862            fn d() {
 7863                // e
 7864                // f
 7865            }
 7866        }
 7867
 7868        fn g() {
 7869            // h
 7870        }
 7871    "
 7872        .unindent(),
 7873    );
 7874
 7875    let expected_display_text = "
 7876        impl A {
 7877            // this is an uncommitted comment
 7878
 7879            fn b() {
 7880 7881            }
 7882
 7883            // this is another uncommitted comment
 7884
 7885            fn d() {
 7886 7887            }
 7888        }
 7889
 7890        fn g() {
 7891 7892        }
 7893        "
 7894    .unindent();
 7895
 7896    cx.update_editor(|editor, window, cx| {
 7897        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 7898        assert_eq!(editor.display_text(cx), expected_display_text);
 7899    });
 7900}
 7901
 7902#[gpui::test]
 7903async fn test_autoindent(cx: &mut TestAppContext) {
 7904    init_test(cx, |_| {});
 7905
 7906    let language = Arc::new(
 7907        Language::new(
 7908            LanguageConfig {
 7909                brackets: BracketPairConfig {
 7910                    pairs: vec![
 7911                        BracketPair {
 7912                            start: "{".to_string(),
 7913                            end: "}".to_string(),
 7914                            close: false,
 7915                            surround: false,
 7916                            newline: true,
 7917                        },
 7918                        BracketPair {
 7919                            start: "(".to_string(),
 7920                            end: ")".to_string(),
 7921                            close: false,
 7922                            surround: false,
 7923                            newline: true,
 7924                        },
 7925                    ],
 7926                    ..Default::default()
 7927                },
 7928                ..Default::default()
 7929            },
 7930            Some(tree_sitter_rust::LANGUAGE.into()),
 7931        )
 7932        .with_indents_query(
 7933            r#"
 7934                (_ "(" ")" @end) @indent
 7935                (_ "{" "}" @end) @indent
 7936            "#,
 7937        )
 7938        .unwrap(),
 7939    );
 7940
 7941    let text = "fn a() {}";
 7942
 7943    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7944    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7945    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7946    editor
 7947        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7948        .await;
 7949
 7950    editor.update_in(cx, |editor, window, cx| {
 7951        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7952            s.select_ranges([5..5, 8..8, 9..9])
 7953        });
 7954        editor.newline(&Newline, window, cx);
 7955        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 7956        assert_eq!(
 7957            editor.selections.ranges(cx),
 7958            &[
 7959                Point::new(1, 4)..Point::new(1, 4),
 7960                Point::new(3, 4)..Point::new(3, 4),
 7961                Point::new(5, 0)..Point::new(5, 0)
 7962            ]
 7963        );
 7964    });
 7965}
 7966
 7967#[gpui::test]
 7968async fn test_autoindent_selections(cx: &mut TestAppContext) {
 7969    init_test(cx, |_| {});
 7970
 7971    {
 7972        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 7973        cx.set_state(indoc! {"
 7974            impl A {
 7975
 7976                fn b() {}
 7977
 7978            «fn c() {
 7979
 7980            }ˇ»
 7981            }
 7982        "});
 7983
 7984        cx.update_editor(|editor, window, cx| {
 7985            editor.autoindent(&Default::default(), window, cx);
 7986        });
 7987
 7988        cx.assert_editor_state(indoc! {"
 7989            impl A {
 7990
 7991                fn b() {}
 7992
 7993                «fn c() {
 7994
 7995                }ˇ»
 7996            }
 7997        "});
 7998    }
 7999
 8000    {
 8001        let mut cx = EditorTestContext::new_multibuffer(
 8002            cx,
 8003            [indoc! { "
 8004                impl A {
 8005                «
 8006                // a
 8007                fn b(){}
 8008                »
 8009                «
 8010                    }
 8011                    fn c(){}
 8012                »
 8013            "}],
 8014        );
 8015
 8016        let buffer = cx.update_editor(|editor, _, cx| {
 8017            let buffer = editor.buffer().update(cx, |buffer, _| {
 8018                buffer.all_buffers().iter().next().unwrap().clone()
 8019            });
 8020            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8021            buffer
 8022        });
 8023
 8024        cx.run_until_parked();
 8025        cx.update_editor(|editor, window, cx| {
 8026            editor.select_all(&Default::default(), window, cx);
 8027            editor.autoindent(&Default::default(), window, cx)
 8028        });
 8029        cx.run_until_parked();
 8030
 8031        cx.update(|_, cx| {
 8032            assert_eq!(
 8033                buffer.read(cx).text(),
 8034                indoc! { "
 8035                    impl A {
 8036
 8037                        // a
 8038                        fn b(){}
 8039
 8040
 8041                    }
 8042                    fn c(){}
 8043
 8044                " }
 8045            )
 8046        });
 8047    }
 8048}
 8049
 8050#[gpui::test]
 8051async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8052    init_test(cx, |_| {});
 8053
 8054    let mut cx = EditorTestContext::new(cx).await;
 8055
 8056    let language = Arc::new(Language::new(
 8057        LanguageConfig {
 8058            brackets: BracketPairConfig {
 8059                pairs: vec![
 8060                    BracketPair {
 8061                        start: "{".to_string(),
 8062                        end: "}".to_string(),
 8063                        close: true,
 8064                        surround: true,
 8065                        newline: true,
 8066                    },
 8067                    BracketPair {
 8068                        start: "(".to_string(),
 8069                        end: ")".to_string(),
 8070                        close: true,
 8071                        surround: true,
 8072                        newline: true,
 8073                    },
 8074                    BracketPair {
 8075                        start: "/*".to_string(),
 8076                        end: " */".to_string(),
 8077                        close: true,
 8078                        surround: true,
 8079                        newline: true,
 8080                    },
 8081                    BracketPair {
 8082                        start: "[".to_string(),
 8083                        end: "]".to_string(),
 8084                        close: false,
 8085                        surround: false,
 8086                        newline: true,
 8087                    },
 8088                    BracketPair {
 8089                        start: "\"".to_string(),
 8090                        end: "\"".to_string(),
 8091                        close: true,
 8092                        surround: true,
 8093                        newline: false,
 8094                    },
 8095                    BracketPair {
 8096                        start: "<".to_string(),
 8097                        end: ">".to_string(),
 8098                        close: false,
 8099                        surround: true,
 8100                        newline: true,
 8101                    },
 8102                ],
 8103                ..Default::default()
 8104            },
 8105            autoclose_before: "})]".to_string(),
 8106            ..Default::default()
 8107        },
 8108        Some(tree_sitter_rust::LANGUAGE.into()),
 8109    ));
 8110
 8111    cx.language_registry().add(language.clone());
 8112    cx.update_buffer(|buffer, cx| {
 8113        buffer.set_language(Some(language), cx);
 8114    });
 8115
 8116    cx.set_state(
 8117        &r#"
 8118            🏀ˇ
 8119            εˇ
 8120            ❤️ˇ
 8121        "#
 8122        .unindent(),
 8123    );
 8124
 8125    // autoclose multiple nested brackets at multiple cursors
 8126    cx.update_editor(|editor, window, cx| {
 8127        editor.handle_input("{", window, cx);
 8128        editor.handle_input("{", window, cx);
 8129        editor.handle_input("{", window, cx);
 8130    });
 8131    cx.assert_editor_state(
 8132        &"
 8133            🏀{{{ˇ}}}
 8134            ε{{{ˇ}}}
 8135            ❤️{{{ˇ}}}
 8136        "
 8137        .unindent(),
 8138    );
 8139
 8140    // insert a different closing bracket
 8141    cx.update_editor(|editor, window, cx| {
 8142        editor.handle_input(")", window, cx);
 8143    });
 8144    cx.assert_editor_state(
 8145        &"
 8146            🏀{{{)ˇ}}}
 8147            ε{{{)ˇ}}}
 8148            ❤️{{{)ˇ}}}
 8149        "
 8150        .unindent(),
 8151    );
 8152
 8153    // skip over the auto-closed brackets when typing a closing bracket
 8154    cx.update_editor(|editor, window, cx| {
 8155        editor.move_right(&MoveRight, window, cx);
 8156        editor.handle_input("}", window, cx);
 8157        editor.handle_input("}", window, cx);
 8158        editor.handle_input("}", window, cx);
 8159    });
 8160    cx.assert_editor_state(
 8161        &"
 8162            🏀{{{)}}}}ˇ
 8163            ε{{{)}}}}ˇ
 8164            ❤️{{{)}}}}ˇ
 8165        "
 8166        .unindent(),
 8167    );
 8168
 8169    // autoclose multi-character pairs
 8170    cx.set_state(
 8171        &"
 8172            ˇ
 8173            ˇ
 8174        "
 8175        .unindent(),
 8176    );
 8177    cx.update_editor(|editor, window, cx| {
 8178        editor.handle_input("/", window, cx);
 8179        editor.handle_input("*", window, cx);
 8180    });
 8181    cx.assert_editor_state(
 8182        &"
 8183            /*ˇ */
 8184            /*ˇ */
 8185        "
 8186        .unindent(),
 8187    );
 8188
 8189    // one cursor autocloses a multi-character pair, one cursor
 8190    // does not autoclose.
 8191    cx.set_state(
 8192        &"
 8193 8194            ˇ
 8195        "
 8196        .unindent(),
 8197    );
 8198    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8199    cx.assert_editor_state(
 8200        &"
 8201            /*ˇ */
 8202 8203        "
 8204        .unindent(),
 8205    );
 8206
 8207    // Don't autoclose if the next character isn't whitespace and isn't
 8208    // listed in the language's "autoclose_before" section.
 8209    cx.set_state("ˇa b");
 8210    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8211    cx.assert_editor_state("{ˇa b");
 8212
 8213    // Don't autoclose if `close` is false for the bracket pair
 8214    cx.set_state("ˇ");
 8215    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8216    cx.assert_editor_state("");
 8217
 8218    // Surround with brackets if text is selected
 8219    cx.set_state("«aˇ» b");
 8220    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8221    cx.assert_editor_state("{«aˇ»} b");
 8222
 8223    // Autoclose when not immediately after a word character
 8224    cx.set_state("a ˇ");
 8225    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8226    cx.assert_editor_state("a \"ˇ\"");
 8227
 8228    // Autoclose pair where the start and end characters are the same
 8229    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8230    cx.assert_editor_state("a \"\"ˇ");
 8231
 8232    // Don't autoclose when immediately after a word character
 8233    cx.set_state("");
 8234    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8235    cx.assert_editor_state("a\"ˇ");
 8236
 8237    // Do autoclose when after a non-word character
 8238    cx.set_state("");
 8239    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8240    cx.assert_editor_state("{\"ˇ\"");
 8241
 8242    // Non identical pairs autoclose regardless of preceding character
 8243    cx.set_state("");
 8244    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8245    cx.assert_editor_state("a{ˇ}");
 8246
 8247    // Don't autoclose pair if autoclose is disabled
 8248    cx.set_state("ˇ");
 8249    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8250    cx.assert_editor_state("");
 8251
 8252    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8253    cx.set_state("«aˇ» b");
 8254    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8255    cx.assert_editor_state("<«aˇ»> b");
 8256}
 8257
 8258#[gpui::test]
 8259async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8260    init_test(cx, |settings| {
 8261        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8262    });
 8263
 8264    let mut cx = EditorTestContext::new(cx).await;
 8265
 8266    let language = Arc::new(Language::new(
 8267        LanguageConfig {
 8268            brackets: BracketPairConfig {
 8269                pairs: vec![
 8270                    BracketPair {
 8271                        start: "{".to_string(),
 8272                        end: "}".to_string(),
 8273                        close: true,
 8274                        surround: true,
 8275                        newline: true,
 8276                    },
 8277                    BracketPair {
 8278                        start: "(".to_string(),
 8279                        end: ")".to_string(),
 8280                        close: true,
 8281                        surround: true,
 8282                        newline: true,
 8283                    },
 8284                    BracketPair {
 8285                        start: "[".to_string(),
 8286                        end: "]".to_string(),
 8287                        close: false,
 8288                        surround: false,
 8289                        newline: true,
 8290                    },
 8291                ],
 8292                ..Default::default()
 8293            },
 8294            autoclose_before: "})]".to_string(),
 8295            ..Default::default()
 8296        },
 8297        Some(tree_sitter_rust::LANGUAGE.into()),
 8298    ));
 8299
 8300    cx.language_registry().add(language.clone());
 8301    cx.update_buffer(|buffer, cx| {
 8302        buffer.set_language(Some(language), cx);
 8303    });
 8304
 8305    cx.set_state(
 8306        &"
 8307            ˇ
 8308            ˇ
 8309            ˇ
 8310        "
 8311        .unindent(),
 8312    );
 8313
 8314    // ensure only matching closing brackets are skipped over
 8315    cx.update_editor(|editor, window, cx| {
 8316        editor.handle_input("}", window, cx);
 8317        editor.move_left(&MoveLeft, window, cx);
 8318        editor.handle_input(")", window, cx);
 8319        editor.move_left(&MoveLeft, window, cx);
 8320    });
 8321    cx.assert_editor_state(
 8322        &"
 8323            ˇ)}
 8324            ˇ)}
 8325            ˇ)}
 8326        "
 8327        .unindent(),
 8328    );
 8329
 8330    // skip-over closing brackets at multiple cursors
 8331    cx.update_editor(|editor, window, cx| {
 8332        editor.handle_input(")", window, cx);
 8333        editor.handle_input("}", window, cx);
 8334    });
 8335    cx.assert_editor_state(
 8336        &"
 8337            )}ˇ
 8338            )}ˇ
 8339            )}ˇ
 8340        "
 8341        .unindent(),
 8342    );
 8343
 8344    // ignore non-close brackets
 8345    cx.update_editor(|editor, window, cx| {
 8346        editor.handle_input("]", window, cx);
 8347        editor.move_left(&MoveLeft, window, cx);
 8348        editor.handle_input("]", window, cx);
 8349    });
 8350    cx.assert_editor_state(
 8351        &"
 8352            )}]ˇ]
 8353            )}]ˇ]
 8354            )}]ˇ]
 8355        "
 8356        .unindent(),
 8357    );
 8358}
 8359
 8360#[gpui::test]
 8361async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8362    init_test(cx, |_| {});
 8363
 8364    let mut cx = EditorTestContext::new(cx).await;
 8365
 8366    let html_language = Arc::new(
 8367        Language::new(
 8368            LanguageConfig {
 8369                name: "HTML".into(),
 8370                brackets: BracketPairConfig {
 8371                    pairs: vec![
 8372                        BracketPair {
 8373                            start: "<".into(),
 8374                            end: ">".into(),
 8375                            close: true,
 8376                            ..Default::default()
 8377                        },
 8378                        BracketPair {
 8379                            start: "{".into(),
 8380                            end: "}".into(),
 8381                            close: true,
 8382                            ..Default::default()
 8383                        },
 8384                        BracketPair {
 8385                            start: "(".into(),
 8386                            end: ")".into(),
 8387                            close: true,
 8388                            ..Default::default()
 8389                        },
 8390                    ],
 8391                    ..Default::default()
 8392                },
 8393                autoclose_before: "})]>".into(),
 8394                ..Default::default()
 8395            },
 8396            Some(tree_sitter_html::LANGUAGE.into()),
 8397        )
 8398        .with_injection_query(
 8399            r#"
 8400            (script_element
 8401                (raw_text) @injection.content
 8402                (#set! injection.language "javascript"))
 8403            "#,
 8404        )
 8405        .unwrap(),
 8406    );
 8407
 8408    let javascript_language = Arc::new(Language::new(
 8409        LanguageConfig {
 8410            name: "JavaScript".into(),
 8411            brackets: BracketPairConfig {
 8412                pairs: vec![
 8413                    BracketPair {
 8414                        start: "/*".into(),
 8415                        end: " */".into(),
 8416                        close: true,
 8417                        ..Default::default()
 8418                    },
 8419                    BracketPair {
 8420                        start: "{".into(),
 8421                        end: "}".into(),
 8422                        close: true,
 8423                        ..Default::default()
 8424                    },
 8425                    BracketPair {
 8426                        start: "(".into(),
 8427                        end: ")".into(),
 8428                        close: true,
 8429                        ..Default::default()
 8430                    },
 8431                ],
 8432                ..Default::default()
 8433            },
 8434            autoclose_before: "})]>".into(),
 8435            ..Default::default()
 8436        },
 8437        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8438    ));
 8439
 8440    cx.language_registry().add(html_language.clone());
 8441    cx.language_registry().add(javascript_language.clone());
 8442
 8443    cx.update_buffer(|buffer, cx| {
 8444        buffer.set_language(Some(html_language), cx);
 8445    });
 8446
 8447    cx.set_state(
 8448        &r#"
 8449            <body>ˇ
 8450                <script>
 8451                    var x = 1;ˇ
 8452                </script>
 8453            </body>ˇ
 8454        "#
 8455        .unindent(),
 8456    );
 8457
 8458    // Precondition: different languages are active at different locations.
 8459    cx.update_editor(|editor, window, cx| {
 8460        let snapshot = editor.snapshot(window, cx);
 8461        let cursors = editor.selections.ranges::<usize>(cx);
 8462        let languages = cursors
 8463            .iter()
 8464            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8465            .collect::<Vec<_>>();
 8466        assert_eq!(
 8467            languages,
 8468            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8469        );
 8470    });
 8471
 8472    // Angle brackets autoclose in HTML, but not JavaScript.
 8473    cx.update_editor(|editor, window, cx| {
 8474        editor.handle_input("<", window, cx);
 8475        editor.handle_input("a", window, cx);
 8476    });
 8477    cx.assert_editor_state(
 8478        &r#"
 8479            <body><aˇ>
 8480                <script>
 8481                    var x = 1;<aˇ
 8482                </script>
 8483            </body><aˇ>
 8484        "#
 8485        .unindent(),
 8486    );
 8487
 8488    // Curly braces and parens autoclose in both HTML and JavaScript.
 8489    cx.update_editor(|editor, window, cx| {
 8490        editor.handle_input(" b=", window, cx);
 8491        editor.handle_input("{", window, cx);
 8492        editor.handle_input("c", window, cx);
 8493        editor.handle_input("(", window, cx);
 8494    });
 8495    cx.assert_editor_state(
 8496        &r#"
 8497            <body><a b={c(ˇ)}>
 8498                <script>
 8499                    var x = 1;<a b={c(ˇ)}
 8500                </script>
 8501            </body><a b={c(ˇ)}>
 8502        "#
 8503        .unindent(),
 8504    );
 8505
 8506    // Brackets that were already autoclosed are skipped.
 8507    cx.update_editor(|editor, window, cx| {
 8508        editor.handle_input(")", window, cx);
 8509        editor.handle_input("d", window, cx);
 8510        editor.handle_input("}", window, cx);
 8511    });
 8512    cx.assert_editor_state(
 8513        &r#"
 8514            <body><a b={c()d}ˇ>
 8515                <script>
 8516                    var x = 1;<a b={c()d}ˇ
 8517                </script>
 8518            </body><a b={c()d}ˇ>
 8519        "#
 8520        .unindent(),
 8521    );
 8522    cx.update_editor(|editor, window, cx| {
 8523        editor.handle_input(">", window, cx);
 8524    });
 8525    cx.assert_editor_state(
 8526        &r#"
 8527            <body><a b={c()d}>ˇ
 8528                <script>
 8529                    var x = 1;<a b={c()d}>ˇ
 8530                </script>
 8531            </body><a b={c()d}>ˇ
 8532        "#
 8533        .unindent(),
 8534    );
 8535
 8536    // Reset
 8537    cx.set_state(
 8538        &r#"
 8539            <body>ˇ
 8540                <script>
 8541                    var x = 1;ˇ
 8542                </script>
 8543            </body>ˇ
 8544        "#
 8545        .unindent(),
 8546    );
 8547
 8548    cx.update_editor(|editor, window, cx| {
 8549        editor.handle_input("<", window, cx);
 8550    });
 8551    cx.assert_editor_state(
 8552        &r#"
 8553            <body><ˇ>
 8554                <script>
 8555                    var x = 1;<ˇ
 8556                </script>
 8557            </body><ˇ>
 8558        "#
 8559        .unindent(),
 8560    );
 8561
 8562    // When backspacing, the closing angle brackets are removed.
 8563    cx.update_editor(|editor, window, cx| {
 8564        editor.backspace(&Backspace, window, cx);
 8565    });
 8566    cx.assert_editor_state(
 8567        &r#"
 8568            <body>ˇ
 8569                <script>
 8570                    var x = 1;ˇ
 8571                </script>
 8572            </body>ˇ
 8573        "#
 8574        .unindent(),
 8575    );
 8576
 8577    // Block comments autoclose in JavaScript, but not HTML.
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.handle_input("/", window, cx);
 8580        editor.handle_input("*", window, cx);
 8581    });
 8582    cx.assert_editor_state(
 8583        &r#"
 8584            <body>/*ˇ
 8585                <script>
 8586                    var x = 1;/*ˇ */
 8587                </script>
 8588            </body>/*ˇ
 8589        "#
 8590        .unindent(),
 8591    );
 8592}
 8593
 8594#[gpui::test]
 8595async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 8596    init_test(cx, |_| {});
 8597
 8598    let mut cx = EditorTestContext::new(cx).await;
 8599
 8600    let rust_language = Arc::new(
 8601        Language::new(
 8602            LanguageConfig {
 8603                name: "Rust".into(),
 8604                brackets: serde_json::from_value(json!([
 8605                    { "start": "{", "end": "}", "close": true, "newline": true },
 8606                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 8607                ]))
 8608                .unwrap(),
 8609                autoclose_before: "})]>".into(),
 8610                ..Default::default()
 8611            },
 8612            Some(tree_sitter_rust::LANGUAGE.into()),
 8613        )
 8614        .with_override_query("(string_literal) @string")
 8615        .unwrap(),
 8616    );
 8617
 8618    cx.language_registry().add(rust_language.clone());
 8619    cx.update_buffer(|buffer, cx| {
 8620        buffer.set_language(Some(rust_language), cx);
 8621    });
 8622
 8623    cx.set_state(
 8624        &r#"
 8625            let x = ˇ
 8626        "#
 8627        .unindent(),
 8628    );
 8629
 8630    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 8631    cx.update_editor(|editor, window, cx| {
 8632        editor.handle_input("\"", window, cx);
 8633    });
 8634    cx.assert_editor_state(
 8635        &r#"
 8636            let x = "ˇ"
 8637        "#
 8638        .unindent(),
 8639    );
 8640
 8641    // Inserting another quotation mark. The cursor moves across the existing
 8642    // automatically-inserted quotation mark.
 8643    cx.update_editor(|editor, window, cx| {
 8644        editor.handle_input("\"", window, cx);
 8645    });
 8646    cx.assert_editor_state(
 8647        &r#"
 8648            let x = ""ˇ
 8649        "#
 8650        .unindent(),
 8651    );
 8652
 8653    // Reset
 8654    cx.set_state(
 8655        &r#"
 8656            let x = ˇ
 8657        "#
 8658        .unindent(),
 8659    );
 8660
 8661    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 8662    cx.update_editor(|editor, window, cx| {
 8663        editor.handle_input("\"", window, cx);
 8664        editor.handle_input(" ", window, cx);
 8665        editor.move_left(&Default::default(), window, cx);
 8666        editor.handle_input("\\", window, cx);
 8667        editor.handle_input("\"", window, cx);
 8668    });
 8669    cx.assert_editor_state(
 8670        &r#"
 8671            let x = "\"ˇ "
 8672        "#
 8673        .unindent(),
 8674    );
 8675
 8676    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 8677    // mark. Nothing is inserted.
 8678    cx.update_editor(|editor, window, cx| {
 8679        editor.move_right(&Default::default(), window, cx);
 8680        editor.handle_input("\"", window, cx);
 8681    });
 8682    cx.assert_editor_state(
 8683        &r#"
 8684            let x = "\" "ˇ
 8685        "#
 8686        .unindent(),
 8687    );
 8688}
 8689
 8690#[gpui::test]
 8691async fn test_surround_with_pair(cx: &mut TestAppContext) {
 8692    init_test(cx, |_| {});
 8693
 8694    let language = Arc::new(Language::new(
 8695        LanguageConfig {
 8696            brackets: BracketPairConfig {
 8697                pairs: vec![
 8698                    BracketPair {
 8699                        start: "{".to_string(),
 8700                        end: "}".to_string(),
 8701                        close: true,
 8702                        surround: true,
 8703                        newline: true,
 8704                    },
 8705                    BracketPair {
 8706                        start: "/* ".to_string(),
 8707                        end: "*/".to_string(),
 8708                        close: true,
 8709                        surround: true,
 8710                        ..Default::default()
 8711                    },
 8712                ],
 8713                ..Default::default()
 8714            },
 8715            ..Default::default()
 8716        },
 8717        Some(tree_sitter_rust::LANGUAGE.into()),
 8718    ));
 8719
 8720    let text = r#"
 8721        a
 8722        b
 8723        c
 8724    "#
 8725    .unindent();
 8726
 8727    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8728    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8729    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8730    editor
 8731        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8732        .await;
 8733
 8734    editor.update_in(cx, |editor, window, cx| {
 8735        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8736            s.select_display_ranges([
 8737                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8738                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8739                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 8740            ])
 8741        });
 8742
 8743        editor.handle_input("{", window, cx);
 8744        editor.handle_input("{", window, cx);
 8745        editor.handle_input("{", window, cx);
 8746        assert_eq!(
 8747            editor.text(cx),
 8748            "
 8749                {{{a}}}
 8750                {{{b}}}
 8751                {{{c}}}
 8752            "
 8753            .unindent()
 8754        );
 8755        assert_eq!(
 8756            editor.selections.display_ranges(cx),
 8757            [
 8758                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 8759                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 8760                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 8761            ]
 8762        );
 8763
 8764        editor.undo(&Undo, window, cx);
 8765        editor.undo(&Undo, window, cx);
 8766        editor.undo(&Undo, window, cx);
 8767        assert_eq!(
 8768            editor.text(cx),
 8769            "
 8770                a
 8771                b
 8772                c
 8773            "
 8774            .unindent()
 8775        );
 8776        assert_eq!(
 8777            editor.selections.display_ranges(cx),
 8778            [
 8779                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8780                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8781                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8782            ]
 8783        );
 8784
 8785        // Ensure inserting the first character of a multi-byte bracket pair
 8786        // doesn't surround the selections with the bracket.
 8787        editor.handle_input("/", window, cx);
 8788        assert_eq!(
 8789            editor.text(cx),
 8790            "
 8791                /
 8792                /
 8793                /
 8794            "
 8795            .unindent()
 8796        );
 8797        assert_eq!(
 8798            editor.selections.display_ranges(cx),
 8799            [
 8800                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8801                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8802                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8803            ]
 8804        );
 8805
 8806        editor.undo(&Undo, window, cx);
 8807        assert_eq!(
 8808            editor.text(cx),
 8809            "
 8810                a
 8811                b
 8812                c
 8813            "
 8814            .unindent()
 8815        );
 8816        assert_eq!(
 8817            editor.selections.display_ranges(cx),
 8818            [
 8819                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8820                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 8821                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 8822            ]
 8823        );
 8824
 8825        // Ensure inserting the last character of a multi-byte bracket pair
 8826        // doesn't surround the selections with the bracket.
 8827        editor.handle_input("*", window, cx);
 8828        assert_eq!(
 8829            editor.text(cx),
 8830            "
 8831                *
 8832                *
 8833                *
 8834            "
 8835            .unindent()
 8836        );
 8837        assert_eq!(
 8838            editor.selections.display_ranges(cx),
 8839            [
 8840                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 8841                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 8842                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 8843            ]
 8844        );
 8845    });
 8846}
 8847
 8848#[gpui::test]
 8849async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 8850    init_test(cx, |_| {});
 8851
 8852    let language = Arc::new(Language::new(
 8853        LanguageConfig {
 8854            brackets: BracketPairConfig {
 8855                pairs: vec![BracketPair {
 8856                    start: "{".to_string(),
 8857                    end: "}".to_string(),
 8858                    close: true,
 8859                    surround: true,
 8860                    newline: true,
 8861                }],
 8862                ..Default::default()
 8863            },
 8864            autoclose_before: "}".to_string(),
 8865            ..Default::default()
 8866        },
 8867        Some(tree_sitter_rust::LANGUAGE.into()),
 8868    ));
 8869
 8870    let text = r#"
 8871        a
 8872        b
 8873        c
 8874    "#
 8875    .unindent();
 8876
 8877    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8878    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8879    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8880    editor
 8881        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8882        .await;
 8883
 8884    editor.update_in(cx, |editor, window, cx| {
 8885        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8886            s.select_ranges([
 8887                Point::new(0, 1)..Point::new(0, 1),
 8888                Point::new(1, 1)..Point::new(1, 1),
 8889                Point::new(2, 1)..Point::new(2, 1),
 8890            ])
 8891        });
 8892
 8893        editor.handle_input("{", window, cx);
 8894        editor.handle_input("{", window, cx);
 8895        editor.handle_input("_", window, cx);
 8896        assert_eq!(
 8897            editor.text(cx),
 8898            "
 8899                a{{_}}
 8900                b{{_}}
 8901                c{{_}}
 8902            "
 8903            .unindent()
 8904        );
 8905        assert_eq!(
 8906            editor.selections.ranges::<Point>(cx),
 8907            [
 8908                Point::new(0, 4)..Point::new(0, 4),
 8909                Point::new(1, 4)..Point::new(1, 4),
 8910                Point::new(2, 4)..Point::new(2, 4)
 8911            ]
 8912        );
 8913
 8914        editor.backspace(&Default::default(), window, cx);
 8915        editor.backspace(&Default::default(), window, cx);
 8916        assert_eq!(
 8917            editor.text(cx),
 8918            "
 8919                a{}
 8920                b{}
 8921                c{}
 8922            "
 8923            .unindent()
 8924        );
 8925        assert_eq!(
 8926            editor.selections.ranges::<Point>(cx),
 8927            [
 8928                Point::new(0, 2)..Point::new(0, 2),
 8929                Point::new(1, 2)..Point::new(1, 2),
 8930                Point::new(2, 2)..Point::new(2, 2)
 8931            ]
 8932        );
 8933
 8934        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 8935        assert_eq!(
 8936            editor.text(cx),
 8937            "
 8938                a
 8939                b
 8940                c
 8941            "
 8942            .unindent()
 8943        );
 8944        assert_eq!(
 8945            editor.selections.ranges::<Point>(cx),
 8946            [
 8947                Point::new(0, 1)..Point::new(0, 1),
 8948                Point::new(1, 1)..Point::new(1, 1),
 8949                Point::new(2, 1)..Point::new(2, 1)
 8950            ]
 8951        );
 8952    });
 8953}
 8954
 8955#[gpui::test]
 8956async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 8957    init_test(cx, |settings| {
 8958        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8959    });
 8960
 8961    let mut cx = EditorTestContext::new(cx).await;
 8962
 8963    let language = Arc::new(Language::new(
 8964        LanguageConfig {
 8965            brackets: BracketPairConfig {
 8966                pairs: vec![
 8967                    BracketPair {
 8968                        start: "{".to_string(),
 8969                        end: "}".to_string(),
 8970                        close: true,
 8971                        surround: true,
 8972                        newline: true,
 8973                    },
 8974                    BracketPair {
 8975                        start: "(".to_string(),
 8976                        end: ")".to_string(),
 8977                        close: true,
 8978                        surround: true,
 8979                        newline: true,
 8980                    },
 8981                    BracketPair {
 8982                        start: "[".to_string(),
 8983                        end: "]".to_string(),
 8984                        close: false,
 8985                        surround: true,
 8986                        newline: true,
 8987                    },
 8988                ],
 8989                ..Default::default()
 8990            },
 8991            autoclose_before: "})]".to_string(),
 8992            ..Default::default()
 8993        },
 8994        Some(tree_sitter_rust::LANGUAGE.into()),
 8995    ));
 8996
 8997    cx.language_registry().add(language.clone());
 8998    cx.update_buffer(|buffer, cx| {
 8999        buffer.set_language(Some(language), cx);
 9000    });
 9001
 9002    cx.set_state(
 9003        &"
 9004            {(ˇ)}
 9005            [[ˇ]]
 9006            {(ˇ)}
 9007        "
 9008        .unindent(),
 9009    );
 9010
 9011    cx.update_editor(|editor, window, cx| {
 9012        editor.backspace(&Default::default(), window, cx);
 9013        editor.backspace(&Default::default(), window, cx);
 9014    });
 9015
 9016    cx.assert_editor_state(
 9017        &"
 9018            ˇ
 9019            ˇ]]
 9020            ˇ
 9021        "
 9022        .unindent(),
 9023    );
 9024
 9025    cx.update_editor(|editor, window, cx| {
 9026        editor.handle_input("{", window, cx);
 9027        editor.handle_input("{", window, cx);
 9028        editor.move_right(&MoveRight, window, cx);
 9029        editor.move_right(&MoveRight, window, cx);
 9030        editor.move_left(&MoveLeft, window, cx);
 9031        editor.move_left(&MoveLeft, window, cx);
 9032        editor.backspace(&Default::default(), window, cx);
 9033    });
 9034
 9035    cx.assert_editor_state(
 9036        &"
 9037            {ˇ}
 9038            {ˇ}]]
 9039            {ˇ}
 9040        "
 9041        .unindent(),
 9042    );
 9043
 9044    cx.update_editor(|editor, window, cx| {
 9045        editor.backspace(&Default::default(), window, cx);
 9046    });
 9047
 9048    cx.assert_editor_state(
 9049        &"
 9050            ˇ
 9051            ˇ]]
 9052            ˇ
 9053        "
 9054        .unindent(),
 9055    );
 9056}
 9057
 9058#[gpui::test]
 9059async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9060    init_test(cx, |_| {});
 9061
 9062    let language = Arc::new(Language::new(
 9063        LanguageConfig::default(),
 9064        Some(tree_sitter_rust::LANGUAGE.into()),
 9065    ));
 9066
 9067    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9068    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9069    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9070    editor
 9071        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9072        .await;
 9073
 9074    editor.update_in(cx, |editor, window, cx| {
 9075        editor.set_auto_replace_emoji_shortcode(true);
 9076
 9077        editor.handle_input("Hello ", window, cx);
 9078        editor.handle_input(":wave", window, cx);
 9079        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9080
 9081        editor.handle_input(":", window, cx);
 9082        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9083
 9084        editor.handle_input(" :smile", window, cx);
 9085        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9086
 9087        editor.handle_input(":", window, cx);
 9088        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9089
 9090        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9091        editor.handle_input(":wave", window, cx);
 9092        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9093
 9094        editor.handle_input(":", window, cx);
 9095        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9096
 9097        editor.handle_input(":1", window, cx);
 9098        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9099
 9100        editor.handle_input(":", window, cx);
 9101        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9102
 9103        // Ensure shortcode does not get replaced when it is part of a word
 9104        editor.handle_input(" Test:wave", window, cx);
 9105        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9106
 9107        editor.handle_input(":", window, cx);
 9108        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9109
 9110        editor.set_auto_replace_emoji_shortcode(false);
 9111
 9112        // Ensure shortcode does not get replaced when auto replace is off
 9113        editor.handle_input(" :wave", window, cx);
 9114        assert_eq!(
 9115            editor.text(cx),
 9116            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9117        );
 9118
 9119        editor.handle_input(":", window, cx);
 9120        assert_eq!(
 9121            editor.text(cx),
 9122            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9123        );
 9124    });
 9125}
 9126
 9127#[gpui::test]
 9128async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9129    init_test(cx, |_| {});
 9130
 9131    let (text, insertion_ranges) = marked_text_ranges(
 9132        indoc! {"
 9133            ˇ
 9134        "},
 9135        false,
 9136    );
 9137
 9138    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9139    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9140
 9141    _ = editor.update_in(cx, |editor, window, cx| {
 9142        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9143
 9144        editor
 9145            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9146            .unwrap();
 9147
 9148        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9149            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9150            assert_eq!(editor.text(cx), expected_text);
 9151            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9152        }
 9153
 9154        assert(
 9155            editor,
 9156            cx,
 9157            indoc! {"
 9158            type «» =•
 9159            "},
 9160        );
 9161
 9162        assert!(editor.context_menu_visible(), "There should be a matches");
 9163    });
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_snippets(cx: &mut TestAppContext) {
 9168    init_test(cx, |_| {});
 9169
 9170    let mut cx = EditorTestContext::new(cx).await;
 9171
 9172    cx.set_state(indoc! {"
 9173        a.ˇ b
 9174        a.ˇ b
 9175        a.ˇ b
 9176    "});
 9177
 9178    cx.update_editor(|editor, window, cx| {
 9179        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9180        let insertion_ranges = editor
 9181            .selections
 9182            .all(cx)
 9183            .iter()
 9184            .map(|s| s.range().clone())
 9185            .collect::<Vec<_>>();
 9186        editor
 9187            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9188            .unwrap();
 9189    });
 9190
 9191    cx.assert_editor_state(indoc! {"
 9192        a.f(«oneˇ», two, «threeˇ») b
 9193        a.f(«oneˇ», two, «threeˇ») b
 9194        a.f(«oneˇ», two, «threeˇ») b
 9195    "});
 9196
 9197    // Can't move earlier than the first tab stop
 9198    cx.update_editor(|editor, window, cx| {
 9199        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9200    });
 9201    cx.assert_editor_state(indoc! {"
 9202        a.f(«oneˇ», two, «threeˇ») b
 9203        a.f(«oneˇ», two, «threeˇ») b
 9204        a.f(«oneˇ», two, «threeˇ») b
 9205    "});
 9206
 9207    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9208    cx.assert_editor_state(indoc! {"
 9209        a.f(one, «twoˇ», three) b
 9210        a.f(one, «twoˇ», three) b
 9211        a.f(one, «twoˇ», three) b
 9212    "});
 9213
 9214    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9215    cx.assert_editor_state(indoc! {"
 9216        a.f(«oneˇ», two, «threeˇ») b
 9217        a.f(«oneˇ», two, «threeˇ») b
 9218        a.f(«oneˇ», two, «threeˇ») b
 9219    "});
 9220
 9221    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9222    cx.assert_editor_state(indoc! {"
 9223        a.f(one, «twoˇ», three) b
 9224        a.f(one, «twoˇ», three) b
 9225        a.f(one, «twoˇ», three) b
 9226    "});
 9227    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9228    cx.assert_editor_state(indoc! {"
 9229        a.f(one, two, three)ˇ b
 9230        a.f(one, two, three)ˇ b
 9231        a.f(one, two, three)ˇ b
 9232    "});
 9233
 9234    // As soon as the last tab stop is reached, snippet state is gone
 9235    cx.update_editor(|editor, window, cx| {
 9236        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9237    });
 9238    cx.assert_editor_state(indoc! {"
 9239        a.f(one, two, three)ˇ b
 9240        a.f(one, two, three)ˇ b
 9241        a.f(one, two, three)ˇ b
 9242    "});
 9243}
 9244
 9245#[gpui::test]
 9246async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9247    init_test(cx, |_| {});
 9248
 9249    let mut cx = EditorTestContext::new(cx).await;
 9250
 9251    cx.update_editor(|editor, window, cx| {
 9252        let snippet = Snippet::parse(indoc! {"
 9253            /*
 9254             * Multiline comment with leading indentation
 9255             *
 9256             * $1
 9257             */
 9258            $0"})
 9259        .unwrap();
 9260        let insertion_ranges = editor
 9261            .selections
 9262            .all(cx)
 9263            .iter()
 9264            .map(|s| s.range().clone())
 9265            .collect::<Vec<_>>();
 9266        editor
 9267            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9268            .unwrap();
 9269    });
 9270
 9271    cx.assert_editor_state(indoc! {"
 9272        /*
 9273         * Multiline comment with leading indentation
 9274         *
 9275         * ˇ
 9276         */
 9277    "});
 9278
 9279    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9280    cx.assert_editor_state(indoc! {"
 9281        /*
 9282         * Multiline comment with leading indentation
 9283         *
 9284         *•
 9285         */
 9286        ˇ"});
 9287}
 9288
 9289#[gpui::test]
 9290async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9291    init_test(cx, |_| {});
 9292
 9293    let fs = FakeFs::new(cx.executor());
 9294    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9295
 9296    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9297
 9298    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9299    language_registry.add(rust_lang());
 9300    let mut fake_servers = language_registry.register_fake_lsp(
 9301        "Rust",
 9302        FakeLspAdapter {
 9303            capabilities: lsp::ServerCapabilities {
 9304                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9305                ..Default::default()
 9306            },
 9307            ..Default::default()
 9308        },
 9309    );
 9310
 9311    let buffer = project
 9312        .update(cx, |project, cx| {
 9313            project.open_local_buffer(path!("/file.rs"), cx)
 9314        })
 9315        .await
 9316        .unwrap();
 9317
 9318    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9319    let (editor, cx) = cx.add_window_view(|window, cx| {
 9320        build_editor_with_project(project.clone(), buffer, window, cx)
 9321    });
 9322    editor.update_in(cx, |editor, window, cx| {
 9323        editor.set_text("one\ntwo\nthree\n", window, cx)
 9324    });
 9325    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9326
 9327    cx.executor().start_waiting();
 9328    let fake_server = fake_servers.next().await.unwrap();
 9329
 9330    {
 9331        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9332            move |params, _| async move {
 9333                assert_eq!(
 9334                    params.text_document.uri,
 9335                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9336                );
 9337                assert_eq!(params.options.tab_size, 4);
 9338                Ok(Some(vec![lsp::TextEdit::new(
 9339                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9340                    ", ".to_string(),
 9341                )]))
 9342            },
 9343        );
 9344        let save = editor
 9345            .update_in(cx, |editor, window, cx| {
 9346                editor.save(
 9347                    SaveOptions {
 9348                        format: true,
 9349                        autosave: false,
 9350                    },
 9351                    project.clone(),
 9352                    window,
 9353                    cx,
 9354                )
 9355            })
 9356            .unwrap();
 9357        cx.executor().start_waiting();
 9358        save.await;
 9359
 9360        assert_eq!(
 9361            editor.update(cx, |editor, cx| editor.text(cx)),
 9362            "one, two\nthree\n"
 9363        );
 9364        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9365    }
 9366
 9367    {
 9368        editor.update_in(cx, |editor, window, cx| {
 9369            editor.set_text("one\ntwo\nthree\n", window, cx)
 9370        });
 9371        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9372
 9373        // Ensure we can still save even if formatting hangs.
 9374        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9375            move |params, _| async move {
 9376                assert_eq!(
 9377                    params.text_document.uri,
 9378                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9379                );
 9380                futures::future::pending::<()>().await;
 9381                unreachable!()
 9382            },
 9383        );
 9384        let save = editor
 9385            .update_in(cx, |editor, window, cx| {
 9386                editor.save(
 9387                    SaveOptions {
 9388                        format: true,
 9389                        autosave: false,
 9390                    },
 9391                    project.clone(),
 9392                    window,
 9393                    cx,
 9394                )
 9395            })
 9396            .unwrap();
 9397        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9398        cx.executor().start_waiting();
 9399        save.await;
 9400        assert_eq!(
 9401            editor.update(cx, |editor, cx| editor.text(cx)),
 9402            "one\ntwo\nthree\n"
 9403        );
 9404    }
 9405
 9406    // Set rust language override and assert overridden tabsize is sent to language server
 9407    update_test_language_settings(cx, |settings| {
 9408        settings.languages.0.insert(
 9409            "Rust".into(),
 9410            LanguageSettingsContent {
 9411                tab_size: NonZeroU32::new(8),
 9412                ..Default::default()
 9413            },
 9414        );
 9415    });
 9416
 9417    {
 9418        editor.update_in(cx, |editor, window, cx| {
 9419            editor.set_text("somehting_new\n", window, cx)
 9420        });
 9421        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9422        let _formatting_request_signal = fake_server
 9423            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9424                assert_eq!(
 9425                    params.text_document.uri,
 9426                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9427                );
 9428                assert_eq!(params.options.tab_size, 8);
 9429                Ok(Some(vec![]))
 9430            });
 9431        let save = editor
 9432            .update_in(cx, |editor, window, cx| {
 9433                editor.save(
 9434                    SaveOptions {
 9435                        format: true,
 9436                        autosave: false,
 9437                    },
 9438                    project.clone(),
 9439                    window,
 9440                    cx,
 9441                )
 9442            })
 9443            .unwrap();
 9444        cx.executor().start_waiting();
 9445        save.await;
 9446    }
 9447}
 9448
 9449#[gpui::test]
 9450async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9451    init_test(cx, |_| {});
 9452
 9453    let cols = 4;
 9454    let rows = 10;
 9455    let sample_text_1 = sample_text(rows, cols, 'a');
 9456    assert_eq!(
 9457        sample_text_1,
 9458        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9459    );
 9460    let sample_text_2 = sample_text(rows, cols, 'l');
 9461    assert_eq!(
 9462        sample_text_2,
 9463        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9464    );
 9465    let sample_text_3 = sample_text(rows, cols, 'v');
 9466    assert_eq!(
 9467        sample_text_3,
 9468        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9469    );
 9470
 9471    let fs = FakeFs::new(cx.executor());
 9472    fs.insert_tree(
 9473        path!("/a"),
 9474        json!({
 9475            "main.rs": sample_text_1,
 9476            "other.rs": sample_text_2,
 9477            "lib.rs": sample_text_3,
 9478        }),
 9479    )
 9480    .await;
 9481
 9482    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9483    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9484    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9485
 9486    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9487    language_registry.add(rust_lang());
 9488    let mut fake_servers = language_registry.register_fake_lsp(
 9489        "Rust",
 9490        FakeLspAdapter {
 9491            capabilities: lsp::ServerCapabilities {
 9492                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9493                ..Default::default()
 9494            },
 9495            ..Default::default()
 9496        },
 9497    );
 9498
 9499    let worktree = project.update(cx, |project, cx| {
 9500        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 9501        assert_eq!(worktrees.len(), 1);
 9502        worktrees.pop().unwrap()
 9503    });
 9504    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9505
 9506    let buffer_1 = project
 9507        .update(cx, |project, cx| {
 9508            project.open_buffer((worktree_id, "main.rs"), cx)
 9509        })
 9510        .await
 9511        .unwrap();
 9512    let buffer_2 = project
 9513        .update(cx, |project, cx| {
 9514            project.open_buffer((worktree_id, "other.rs"), cx)
 9515        })
 9516        .await
 9517        .unwrap();
 9518    let buffer_3 = project
 9519        .update(cx, |project, cx| {
 9520            project.open_buffer((worktree_id, "lib.rs"), cx)
 9521        })
 9522        .await
 9523        .unwrap();
 9524
 9525    let multi_buffer = cx.new(|cx| {
 9526        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9527        multi_buffer.push_excerpts(
 9528            buffer_1.clone(),
 9529            [
 9530                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9531                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9532                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9533            ],
 9534            cx,
 9535        );
 9536        multi_buffer.push_excerpts(
 9537            buffer_2.clone(),
 9538            [
 9539                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9540                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9541                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9542            ],
 9543            cx,
 9544        );
 9545        multi_buffer.push_excerpts(
 9546            buffer_3.clone(),
 9547            [
 9548                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
 9549                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
 9550                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
 9551            ],
 9552            cx,
 9553        );
 9554        multi_buffer
 9555    });
 9556    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 9557        Editor::new(
 9558            EditorMode::full(),
 9559            multi_buffer,
 9560            Some(project.clone()),
 9561            window,
 9562            cx,
 9563        )
 9564    });
 9565
 9566    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9567        editor.change_selections(
 9568            SelectionEffects::scroll(Autoscroll::Next),
 9569            window,
 9570            cx,
 9571            |s| s.select_ranges(Some(1..2)),
 9572        );
 9573        editor.insert("|one|two|three|", window, cx);
 9574    });
 9575    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9576    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 9577        editor.change_selections(
 9578            SelectionEffects::scroll(Autoscroll::Next),
 9579            window,
 9580            cx,
 9581            |s| s.select_ranges(Some(60..70)),
 9582        );
 9583        editor.insert("|four|five|six|", window, cx);
 9584    });
 9585    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 9586
 9587    // First two buffers should be edited, but not the third one.
 9588    assert_eq!(
 9589        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9590        "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}",
 9591    );
 9592    buffer_1.update(cx, |buffer, _| {
 9593        assert!(buffer.is_dirty());
 9594        assert_eq!(
 9595            buffer.text(),
 9596            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 9597        )
 9598    });
 9599    buffer_2.update(cx, |buffer, _| {
 9600        assert!(buffer.is_dirty());
 9601        assert_eq!(
 9602            buffer.text(),
 9603            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 9604        )
 9605    });
 9606    buffer_3.update(cx, |buffer, _| {
 9607        assert!(!buffer.is_dirty());
 9608        assert_eq!(buffer.text(), sample_text_3,)
 9609    });
 9610    cx.executor().run_until_parked();
 9611
 9612    cx.executor().start_waiting();
 9613    let save = multi_buffer_editor
 9614        .update_in(cx, |editor, window, cx| {
 9615            editor.save(
 9616                SaveOptions {
 9617                    format: true,
 9618                    autosave: false,
 9619                },
 9620                project.clone(),
 9621                window,
 9622                cx,
 9623            )
 9624        })
 9625        .unwrap();
 9626
 9627    let fake_server = fake_servers.next().await.unwrap();
 9628    fake_server
 9629        .server
 9630        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9631            Ok(Some(vec![lsp::TextEdit::new(
 9632                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9633                format!("[{} formatted]", params.text_document.uri),
 9634            )]))
 9635        })
 9636        .detach();
 9637    save.await;
 9638
 9639    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 9640    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 9641    assert_eq!(
 9642        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 9643        uri!(
 9644            "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}"
 9645        ),
 9646    );
 9647    buffer_1.update(cx, |buffer, _| {
 9648        assert!(!buffer.is_dirty());
 9649        assert_eq!(
 9650            buffer.text(),
 9651            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 9652        )
 9653    });
 9654    buffer_2.update(cx, |buffer, _| {
 9655        assert!(!buffer.is_dirty());
 9656        assert_eq!(
 9657            buffer.text(),
 9658            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 9659        )
 9660    });
 9661    buffer_3.update(cx, |buffer, _| {
 9662        assert!(!buffer.is_dirty());
 9663        assert_eq!(buffer.text(), sample_text_3,)
 9664    });
 9665}
 9666
 9667#[gpui::test]
 9668async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
 9669    init_test(cx, |_| {});
 9670
 9671    let fs = FakeFs::new(cx.executor());
 9672    fs.insert_tree(
 9673        path!("/dir"),
 9674        json!({
 9675            "file1.rs": "fn main() { println!(\"hello\"); }",
 9676            "file2.rs": "fn test() { println!(\"test\"); }",
 9677            "file3.rs": "fn other() { println!(\"other\"); }\n",
 9678        }),
 9679    )
 9680    .await;
 9681
 9682    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
 9683    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9684    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 9685
 9686    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9687    language_registry.add(rust_lang());
 9688
 9689    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
 9690    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 9691
 9692    // Open three buffers
 9693    let buffer_1 = project
 9694        .update(cx, |project, cx| {
 9695            project.open_buffer((worktree_id, "file1.rs"), cx)
 9696        })
 9697        .await
 9698        .unwrap();
 9699    let buffer_2 = project
 9700        .update(cx, |project, cx| {
 9701            project.open_buffer((worktree_id, "file2.rs"), cx)
 9702        })
 9703        .await
 9704        .unwrap();
 9705    let buffer_3 = project
 9706        .update(cx, |project, cx| {
 9707            project.open_buffer((worktree_id, "file3.rs"), cx)
 9708        })
 9709        .await
 9710        .unwrap();
 9711
 9712    // Create a multi-buffer with all three buffers
 9713    let multi_buffer = cx.new(|cx| {
 9714        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 9715        multi_buffer.push_excerpts(
 9716            buffer_1.clone(),
 9717            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9718            cx,
 9719        );
 9720        multi_buffer.push_excerpts(
 9721            buffer_2.clone(),
 9722            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9723            cx,
 9724        );
 9725        multi_buffer.push_excerpts(
 9726            buffer_3.clone(),
 9727            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 9728            cx,
 9729        );
 9730        multi_buffer
 9731    });
 9732
 9733    let editor = cx.new_window_entity(|window, cx| {
 9734        Editor::new(
 9735            EditorMode::full(),
 9736            multi_buffer,
 9737            Some(project.clone()),
 9738            window,
 9739            cx,
 9740        )
 9741    });
 9742
 9743    // Edit only the first buffer
 9744    editor.update_in(cx, |editor, window, cx| {
 9745        editor.change_selections(
 9746            SelectionEffects::scroll(Autoscroll::Next),
 9747            window,
 9748            cx,
 9749            |s| s.select_ranges(Some(10..10)),
 9750        );
 9751        editor.insert("// edited", window, cx);
 9752    });
 9753
 9754    // Verify that only buffer 1 is dirty
 9755    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
 9756    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9757    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9758
 9759    // Get write counts after file creation (files were created with initial content)
 9760    // We expect each file to have been written once during creation
 9761    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
 9762    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
 9763    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
 9764
 9765    // Perform autosave
 9766    let save_task = editor.update_in(cx, |editor, window, cx| {
 9767        editor.save(
 9768            SaveOptions {
 9769                format: true,
 9770                autosave: true,
 9771            },
 9772            project.clone(),
 9773            window,
 9774            cx,
 9775        )
 9776    });
 9777    save_task.await.unwrap();
 9778
 9779    // Only the dirty buffer should have been saved
 9780    assert_eq!(
 9781        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9782        1,
 9783        "Buffer 1 was dirty, so it should have been written once during autosave"
 9784    );
 9785    assert_eq!(
 9786        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9787        0,
 9788        "Buffer 2 was clean, so it should not have been written during autosave"
 9789    );
 9790    assert_eq!(
 9791        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9792        0,
 9793        "Buffer 3 was clean, so it should not have been written during autosave"
 9794    );
 9795
 9796    // Verify buffer states after autosave
 9797    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9798    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9799    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
 9800
 9801    // Now perform a manual save (format = true)
 9802    let save_task = editor.update_in(cx, |editor, window, cx| {
 9803        editor.save(
 9804            SaveOptions {
 9805                format: true,
 9806                autosave: false,
 9807            },
 9808            project.clone(),
 9809            window,
 9810            cx,
 9811        )
 9812    });
 9813    save_task.await.unwrap();
 9814
 9815    // During manual save, clean buffers don't get written to disk
 9816    // They just get did_save called for language server notifications
 9817    assert_eq!(
 9818        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
 9819        1,
 9820        "Buffer 1 should only have been written once total (during autosave, not manual save)"
 9821    );
 9822    assert_eq!(
 9823        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
 9824        0,
 9825        "Buffer 2 should not have been written at all"
 9826    );
 9827    assert_eq!(
 9828        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
 9829        0,
 9830        "Buffer 3 should not have been written at all"
 9831    );
 9832}
 9833
 9834#[gpui::test]
 9835async fn test_range_format_during_save(cx: &mut TestAppContext) {
 9836    init_test(cx, |_| {});
 9837
 9838    let fs = FakeFs::new(cx.executor());
 9839    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9840
 9841    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 9842
 9843    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9844    language_registry.add(rust_lang());
 9845    let mut fake_servers = language_registry.register_fake_lsp(
 9846        "Rust",
 9847        FakeLspAdapter {
 9848            capabilities: lsp::ServerCapabilities {
 9849                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 9850                ..Default::default()
 9851            },
 9852            ..Default::default()
 9853        },
 9854    );
 9855
 9856    let buffer = project
 9857        .update(cx, |project, cx| {
 9858            project.open_local_buffer(path!("/file.rs"), cx)
 9859        })
 9860        .await
 9861        .unwrap();
 9862
 9863    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9864    let (editor, cx) = cx.add_window_view(|window, cx| {
 9865        build_editor_with_project(project.clone(), buffer, window, cx)
 9866    });
 9867    editor.update_in(cx, |editor, window, cx| {
 9868        editor.set_text("one\ntwo\nthree\n", window, cx)
 9869    });
 9870    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9871
 9872    cx.executor().start_waiting();
 9873    let fake_server = fake_servers.next().await.unwrap();
 9874
 9875    let save = editor
 9876        .update_in(cx, |editor, window, cx| {
 9877            editor.save(
 9878                SaveOptions {
 9879                    format: true,
 9880                    autosave: false,
 9881                },
 9882                project.clone(),
 9883                window,
 9884                cx,
 9885            )
 9886        })
 9887        .unwrap();
 9888    fake_server
 9889        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 9890            assert_eq!(
 9891                params.text_document.uri,
 9892                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9893            );
 9894            assert_eq!(params.options.tab_size, 4);
 9895            Ok(Some(vec![lsp::TextEdit::new(
 9896                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9897                ", ".to_string(),
 9898            )]))
 9899        })
 9900        .next()
 9901        .await;
 9902    cx.executor().start_waiting();
 9903    save.await;
 9904    assert_eq!(
 9905        editor.update(cx, |editor, cx| editor.text(cx)),
 9906        "one, two\nthree\n"
 9907    );
 9908    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9909
 9910    editor.update_in(cx, |editor, window, cx| {
 9911        editor.set_text("one\ntwo\nthree\n", window, cx)
 9912    });
 9913    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9914
 9915    // Ensure we can still save even if formatting hangs.
 9916    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
 9917        move |params, _| async move {
 9918            assert_eq!(
 9919                params.text_document.uri,
 9920                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9921            );
 9922            futures::future::pending::<()>().await;
 9923            unreachable!()
 9924        },
 9925    );
 9926    let save = editor
 9927        .update_in(cx, |editor, window, cx| {
 9928            editor.save(
 9929                SaveOptions {
 9930                    format: true,
 9931                    autosave: false,
 9932                },
 9933                project.clone(),
 9934                window,
 9935                cx,
 9936            )
 9937        })
 9938        .unwrap();
 9939    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9940    cx.executor().start_waiting();
 9941    save.await;
 9942    assert_eq!(
 9943        editor.update(cx, |editor, cx| editor.text(cx)),
 9944        "one\ntwo\nthree\n"
 9945    );
 9946    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9947
 9948    // For non-dirty buffer, no formatting request should be sent
 9949    let save = editor
 9950        .update_in(cx, |editor, window, cx| {
 9951            editor.save(
 9952                SaveOptions {
 9953                    format: false,
 9954                    autosave: false,
 9955                },
 9956                project.clone(),
 9957                window,
 9958                cx,
 9959            )
 9960        })
 9961        .unwrap();
 9962    let _pending_format_request = fake_server
 9963        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 9964            panic!("Should not be invoked");
 9965        })
 9966        .next();
 9967    cx.executor().start_waiting();
 9968    save.await;
 9969
 9970    // Set Rust language override and assert overridden tabsize is sent to language server
 9971    update_test_language_settings(cx, |settings| {
 9972        settings.languages.0.insert(
 9973            "Rust".into(),
 9974            LanguageSettingsContent {
 9975                tab_size: NonZeroU32::new(8),
 9976                ..Default::default()
 9977            },
 9978        );
 9979    });
 9980
 9981    editor.update_in(cx, |editor, window, cx| {
 9982        editor.set_text("somehting_new\n", window, cx)
 9983    });
 9984    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9985    let save = editor
 9986        .update_in(cx, |editor, window, cx| {
 9987            editor.save(
 9988                SaveOptions {
 9989                    format: true,
 9990                    autosave: false,
 9991                },
 9992                project.clone(),
 9993                window,
 9994                cx,
 9995            )
 9996        })
 9997        .unwrap();
 9998    fake_server
 9999        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10000            assert_eq!(
10001                params.text_document.uri,
10002                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10003            );
10004            assert_eq!(params.options.tab_size, 8);
10005            Ok(Some(Vec::new()))
10006        })
10007        .next()
10008        .await;
10009    save.await;
10010}
10011
10012#[gpui::test]
10013async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10014    init_test(cx, |settings| {
10015        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10016            Formatter::LanguageServer { name: None },
10017        )))
10018    });
10019
10020    let fs = FakeFs::new(cx.executor());
10021    fs.insert_file(path!("/file.rs"), Default::default()).await;
10022
10023    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10024
10025    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10026    language_registry.add(Arc::new(Language::new(
10027        LanguageConfig {
10028            name: "Rust".into(),
10029            matcher: LanguageMatcher {
10030                path_suffixes: vec!["rs".to_string()],
10031                ..Default::default()
10032            },
10033            ..LanguageConfig::default()
10034        },
10035        Some(tree_sitter_rust::LANGUAGE.into()),
10036    )));
10037    update_test_language_settings(cx, |settings| {
10038        // Enable Prettier formatting for the same buffer, and ensure
10039        // LSP is called instead of Prettier.
10040        settings.defaults.prettier = Some(PrettierSettings {
10041            allowed: true,
10042            ..PrettierSettings::default()
10043        });
10044    });
10045    let mut fake_servers = language_registry.register_fake_lsp(
10046        "Rust",
10047        FakeLspAdapter {
10048            capabilities: lsp::ServerCapabilities {
10049                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10050                ..Default::default()
10051            },
10052            ..Default::default()
10053        },
10054    );
10055
10056    let buffer = project
10057        .update(cx, |project, cx| {
10058            project.open_local_buffer(path!("/file.rs"), cx)
10059        })
10060        .await
10061        .unwrap();
10062
10063    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10064    let (editor, cx) = cx.add_window_view(|window, cx| {
10065        build_editor_with_project(project.clone(), buffer, window, cx)
10066    });
10067    editor.update_in(cx, |editor, window, cx| {
10068        editor.set_text("one\ntwo\nthree\n", window, cx)
10069    });
10070
10071    cx.executor().start_waiting();
10072    let fake_server = fake_servers.next().await.unwrap();
10073
10074    let format = editor
10075        .update_in(cx, |editor, window, cx| {
10076            editor.perform_format(
10077                project.clone(),
10078                FormatTrigger::Manual,
10079                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10080                window,
10081                cx,
10082            )
10083        })
10084        .unwrap();
10085    fake_server
10086        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10087            assert_eq!(
10088                params.text_document.uri,
10089                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10090            );
10091            assert_eq!(params.options.tab_size, 4);
10092            Ok(Some(vec![lsp::TextEdit::new(
10093                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10094                ", ".to_string(),
10095            )]))
10096        })
10097        .next()
10098        .await;
10099    cx.executor().start_waiting();
10100    format.await;
10101    assert_eq!(
10102        editor.update(cx, |editor, cx| editor.text(cx)),
10103        "one, two\nthree\n"
10104    );
10105
10106    editor.update_in(cx, |editor, window, cx| {
10107        editor.set_text("one\ntwo\nthree\n", window, cx)
10108    });
10109    // Ensure we don't lock if formatting hangs.
10110    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10111        move |params, _| async move {
10112            assert_eq!(
10113                params.text_document.uri,
10114                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10115            );
10116            futures::future::pending::<()>().await;
10117            unreachable!()
10118        },
10119    );
10120    let format = editor
10121        .update_in(cx, |editor, window, cx| {
10122            editor.perform_format(
10123                project,
10124                FormatTrigger::Manual,
10125                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10126                window,
10127                cx,
10128            )
10129        })
10130        .unwrap();
10131    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10132    cx.executor().start_waiting();
10133    format.await;
10134    assert_eq!(
10135        editor.update(cx, |editor, cx| editor.text(cx)),
10136        "one\ntwo\nthree\n"
10137    );
10138}
10139
10140#[gpui::test]
10141async fn test_multiple_formatters(cx: &mut TestAppContext) {
10142    init_test(cx, |settings| {
10143        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10144        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10145            Formatter::LanguageServer { name: None },
10146            Formatter::CodeActions(
10147                [
10148                    ("code-action-1".into(), true),
10149                    ("code-action-2".into(), true),
10150                ]
10151                .into_iter()
10152                .collect(),
10153            ),
10154        ])))
10155    });
10156
10157    let fs = FakeFs::new(cx.executor());
10158    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10159        .await;
10160
10161    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10162    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10163    language_registry.add(rust_lang());
10164
10165    let mut fake_servers = language_registry.register_fake_lsp(
10166        "Rust",
10167        FakeLspAdapter {
10168            capabilities: lsp::ServerCapabilities {
10169                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10170                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10171                    commands: vec!["the-command-for-code-action-1".into()],
10172                    ..Default::default()
10173                }),
10174                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10175                ..Default::default()
10176            },
10177            ..Default::default()
10178        },
10179    );
10180
10181    let buffer = project
10182        .update(cx, |project, cx| {
10183            project.open_local_buffer(path!("/file.rs"), cx)
10184        })
10185        .await
10186        .unwrap();
10187
10188    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10189    let (editor, cx) = cx.add_window_view(|window, cx| {
10190        build_editor_with_project(project.clone(), buffer, window, cx)
10191    });
10192
10193    cx.executor().start_waiting();
10194
10195    let fake_server = fake_servers.next().await.unwrap();
10196    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10197        move |_params, _| async move {
10198            Ok(Some(vec![lsp::TextEdit::new(
10199                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10200                "applied-formatting\n".to_string(),
10201            )]))
10202        },
10203    );
10204    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10205        move |params, _| async move {
10206            assert_eq!(
10207                params.context.only,
10208                Some(vec!["code-action-1".into(), "code-action-2".into()])
10209            );
10210            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10211            Ok(Some(vec![
10212                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10213                    kind: Some("code-action-1".into()),
10214                    edit: Some(lsp::WorkspaceEdit::new(
10215                        [(
10216                            uri.clone(),
10217                            vec![lsp::TextEdit::new(
10218                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10219                                "applied-code-action-1-edit\n".to_string(),
10220                            )],
10221                        )]
10222                        .into_iter()
10223                        .collect(),
10224                    )),
10225                    command: Some(lsp::Command {
10226                        command: "the-command-for-code-action-1".into(),
10227                        ..Default::default()
10228                    }),
10229                    ..Default::default()
10230                }),
10231                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10232                    kind: Some("code-action-2".into()),
10233                    edit: Some(lsp::WorkspaceEdit::new(
10234                        [(
10235                            uri.clone(),
10236                            vec![lsp::TextEdit::new(
10237                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10238                                "applied-code-action-2-edit\n".to_string(),
10239                            )],
10240                        )]
10241                        .into_iter()
10242                        .collect(),
10243                    )),
10244                    ..Default::default()
10245                }),
10246            ]))
10247        },
10248    );
10249
10250    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10251        move |params, _| async move { Ok(params) }
10252    });
10253
10254    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10255    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10256        let fake = fake_server.clone();
10257        let lock = command_lock.clone();
10258        move |params, _| {
10259            assert_eq!(params.command, "the-command-for-code-action-1");
10260            let fake = fake.clone();
10261            let lock = lock.clone();
10262            async move {
10263                lock.lock().await;
10264                fake.server
10265                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10266                        label: None,
10267                        edit: lsp::WorkspaceEdit {
10268                            changes: Some(
10269                                [(
10270                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10271                                    vec![lsp::TextEdit {
10272                                        range: lsp::Range::new(
10273                                            lsp::Position::new(0, 0),
10274                                            lsp::Position::new(0, 0),
10275                                        ),
10276                                        new_text: "applied-code-action-1-command\n".into(),
10277                                    }],
10278                                )]
10279                                .into_iter()
10280                                .collect(),
10281                            ),
10282                            ..Default::default()
10283                        },
10284                    })
10285                    .await
10286                    .into_response()
10287                    .unwrap();
10288                Ok(Some(json!(null)))
10289            }
10290        }
10291    });
10292
10293    cx.executor().start_waiting();
10294    editor
10295        .update_in(cx, |editor, window, cx| {
10296            editor.perform_format(
10297                project.clone(),
10298                FormatTrigger::Manual,
10299                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10300                window,
10301                cx,
10302            )
10303        })
10304        .unwrap()
10305        .await;
10306    editor.update(cx, |editor, cx| {
10307        assert_eq!(
10308            editor.text(cx),
10309            r#"
10310                applied-code-action-2-edit
10311                applied-code-action-1-command
10312                applied-code-action-1-edit
10313                applied-formatting
10314                one
10315                two
10316                three
10317            "#
10318            .unindent()
10319        );
10320    });
10321
10322    editor.update_in(cx, |editor, window, cx| {
10323        editor.undo(&Default::default(), window, cx);
10324        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10325    });
10326
10327    // Perform a manual edit while waiting for an LSP command
10328    // that's being run as part of a formatting code action.
10329    let lock_guard = command_lock.lock().await;
10330    let format = editor
10331        .update_in(cx, |editor, window, cx| {
10332            editor.perform_format(
10333                project.clone(),
10334                FormatTrigger::Manual,
10335                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10336                window,
10337                cx,
10338            )
10339        })
10340        .unwrap();
10341    cx.run_until_parked();
10342    editor.update(cx, |editor, cx| {
10343        assert_eq!(
10344            editor.text(cx),
10345            r#"
10346                applied-code-action-1-edit
10347                applied-formatting
10348                one
10349                two
10350                three
10351            "#
10352            .unindent()
10353        );
10354
10355        editor.buffer.update(cx, |buffer, cx| {
10356            let ix = buffer.len(cx);
10357            buffer.edit([(ix..ix, "edited\n")], None, cx);
10358        });
10359    });
10360
10361    // Allow the LSP command to proceed. Because the buffer was edited,
10362    // the second code action will not be run.
10363    drop(lock_guard);
10364    format.await;
10365    editor.update_in(cx, |editor, window, cx| {
10366        assert_eq!(
10367            editor.text(cx),
10368            r#"
10369                applied-code-action-1-command
10370                applied-code-action-1-edit
10371                applied-formatting
10372                one
10373                two
10374                three
10375                edited
10376            "#
10377            .unindent()
10378        );
10379
10380        // The manual edit is undone first, because it is the last thing the user did
10381        // (even though the command completed afterwards).
10382        editor.undo(&Default::default(), window, cx);
10383        assert_eq!(
10384            editor.text(cx),
10385            r#"
10386                applied-code-action-1-command
10387                applied-code-action-1-edit
10388                applied-formatting
10389                one
10390                two
10391                three
10392            "#
10393            .unindent()
10394        );
10395
10396        // All the formatting (including the command, which completed after the manual edit)
10397        // is undone together.
10398        editor.undo(&Default::default(), window, cx);
10399        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10400    });
10401}
10402
10403#[gpui::test]
10404async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10405    init_test(cx, |settings| {
10406        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10407            Formatter::LanguageServer { name: None },
10408        ])))
10409    });
10410
10411    let fs = FakeFs::new(cx.executor());
10412    fs.insert_file(path!("/file.ts"), Default::default()).await;
10413
10414    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10415
10416    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10417    language_registry.add(Arc::new(Language::new(
10418        LanguageConfig {
10419            name: "TypeScript".into(),
10420            matcher: LanguageMatcher {
10421                path_suffixes: vec!["ts".to_string()],
10422                ..Default::default()
10423            },
10424            ..LanguageConfig::default()
10425        },
10426        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10427    )));
10428    update_test_language_settings(cx, |settings| {
10429        settings.defaults.prettier = Some(PrettierSettings {
10430            allowed: true,
10431            ..PrettierSettings::default()
10432        });
10433    });
10434    let mut fake_servers = language_registry.register_fake_lsp(
10435        "TypeScript",
10436        FakeLspAdapter {
10437            capabilities: lsp::ServerCapabilities {
10438                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10439                ..Default::default()
10440            },
10441            ..Default::default()
10442        },
10443    );
10444
10445    let buffer = project
10446        .update(cx, |project, cx| {
10447            project.open_local_buffer(path!("/file.ts"), cx)
10448        })
10449        .await
10450        .unwrap();
10451
10452    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10453    let (editor, cx) = cx.add_window_view(|window, cx| {
10454        build_editor_with_project(project.clone(), buffer, window, cx)
10455    });
10456    editor.update_in(cx, |editor, window, cx| {
10457        editor.set_text(
10458            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10459            window,
10460            cx,
10461        )
10462    });
10463
10464    cx.executor().start_waiting();
10465    let fake_server = fake_servers.next().await.unwrap();
10466
10467    let format = editor
10468        .update_in(cx, |editor, window, cx| {
10469            editor.perform_code_action_kind(
10470                project.clone(),
10471                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10472                window,
10473                cx,
10474            )
10475        })
10476        .unwrap();
10477    fake_server
10478        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
10479            assert_eq!(
10480                params.text_document.uri,
10481                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10482            );
10483            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
10484                lsp::CodeAction {
10485                    title: "Organize Imports".to_string(),
10486                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
10487                    edit: Some(lsp::WorkspaceEdit {
10488                        changes: Some(
10489                            [(
10490                                params.text_document.uri.clone(),
10491                                vec![lsp::TextEdit::new(
10492                                    lsp::Range::new(
10493                                        lsp::Position::new(1, 0),
10494                                        lsp::Position::new(2, 0),
10495                                    ),
10496                                    "".to_string(),
10497                                )],
10498                            )]
10499                            .into_iter()
10500                            .collect(),
10501                        ),
10502                        ..Default::default()
10503                    }),
10504                    ..Default::default()
10505                },
10506            )]))
10507        })
10508        .next()
10509        .await;
10510    cx.executor().start_waiting();
10511    format.await;
10512    assert_eq!(
10513        editor.update(cx, |editor, cx| editor.text(cx)),
10514        "import { a } from 'module';\n\nconst x = a;\n"
10515    );
10516
10517    editor.update_in(cx, |editor, window, cx| {
10518        editor.set_text(
10519            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
10520            window,
10521            cx,
10522        )
10523    });
10524    // Ensure we don't lock if code action hangs.
10525    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10526        move |params, _| async move {
10527            assert_eq!(
10528                params.text_document.uri,
10529                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
10530            );
10531            futures::future::pending::<()>().await;
10532            unreachable!()
10533        },
10534    );
10535    let format = editor
10536        .update_in(cx, |editor, window, cx| {
10537            editor.perform_code_action_kind(
10538                project,
10539                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
10540                window,
10541                cx,
10542            )
10543        })
10544        .unwrap();
10545    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
10546    cx.executor().start_waiting();
10547    format.await;
10548    assert_eq!(
10549        editor.update(cx, |editor, cx| editor.text(cx)),
10550        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
10551    );
10552}
10553
10554#[gpui::test]
10555async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
10556    init_test(cx, |_| {});
10557
10558    let mut cx = EditorLspTestContext::new_rust(
10559        lsp::ServerCapabilities {
10560            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10561            ..Default::default()
10562        },
10563        cx,
10564    )
10565    .await;
10566
10567    cx.set_state(indoc! {"
10568        one.twoˇ
10569    "});
10570
10571    // The format request takes a long time. When it completes, it inserts
10572    // a newline and an indent before the `.`
10573    cx.lsp
10574        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
10575            let executor = cx.background_executor().clone();
10576            async move {
10577                executor.timer(Duration::from_millis(100)).await;
10578                Ok(Some(vec![lsp::TextEdit {
10579                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
10580                    new_text: "\n    ".into(),
10581                }]))
10582            }
10583        });
10584
10585    // Submit a format request.
10586    let format_1 = cx
10587        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10588        .unwrap();
10589    cx.executor().run_until_parked();
10590
10591    // Submit a second format request.
10592    let format_2 = cx
10593        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10594        .unwrap();
10595    cx.executor().run_until_parked();
10596
10597    // Wait for both format requests to complete
10598    cx.executor().advance_clock(Duration::from_millis(200));
10599    cx.executor().start_waiting();
10600    format_1.await.unwrap();
10601    cx.executor().start_waiting();
10602    format_2.await.unwrap();
10603
10604    // The formatting edits only happens once.
10605    cx.assert_editor_state(indoc! {"
10606        one
10607            .twoˇ
10608    "});
10609}
10610
10611#[gpui::test]
10612async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
10613    init_test(cx, |settings| {
10614        settings.defaults.formatter = Some(SelectedFormatter::Auto)
10615    });
10616
10617    let mut cx = EditorLspTestContext::new_rust(
10618        lsp::ServerCapabilities {
10619            document_formatting_provider: Some(lsp::OneOf::Left(true)),
10620            ..Default::default()
10621        },
10622        cx,
10623    )
10624    .await;
10625
10626    // Set up a buffer white some trailing whitespace and no trailing newline.
10627    cx.set_state(
10628        &[
10629            "one ",   //
10630            "twoˇ",   //
10631            "three ", //
10632            "four",   //
10633        ]
10634        .join("\n"),
10635    );
10636
10637    // Submit a format request.
10638    let format = cx
10639        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
10640        .unwrap();
10641
10642    // Record which buffer changes have been sent to the language server
10643    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
10644    cx.lsp
10645        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
10646            let buffer_changes = buffer_changes.clone();
10647            move |params, _| {
10648                buffer_changes.lock().extend(
10649                    params
10650                        .content_changes
10651                        .into_iter()
10652                        .map(|e| (e.range.unwrap(), e.text)),
10653                );
10654            }
10655        });
10656
10657    // Handle formatting requests to the language server.
10658    cx.lsp
10659        .set_request_handler::<lsp::request::Formatting, _, _>({
10660            let buffer_changes = buffer_changes.clone();
10661            move |_, _| {
10662                // When formatting is requested, trailing whitespace has already been stripped,
10663                // and the trailing newline has already been added.
10664                assert_eq!(
10665                    &buffer_changes.lock()[1..],
10666                    &[
10667                        (
10668                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
10669                            "".into()
10670                        ),
10671                        (
10672                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
10673                            "".into()
10674                        ),
10675                        (
10676                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
10677                            "\n".into()
10678                        ),
10679                    ]
10680                );
10681
10682                // Insert blank lines between each line of the buffer.
10683                async move {
10684                    Ok(Some(vec![
10685                        lsp::TextEdit {
10686                            range: lsp::Range::new(
10687                                lsp::Position::new(1, 0),
10688                                lsp::Position::new(1, 0),
10689                            ),
10690                            new_text: "\n".into(),
10691                        },
10692                        lsp::TextEdit {
10693                            range: lsp::Range::new(
10694                                lsp::Position::new(2, 0),
10695                                lsp::Position::new(2, 0),
10696                            ),
10697                            new_text: "\n".into(),
10698                        },
10699                    ]))
10700                }
10701            }
10702        });
10703
10704    // After formatting the buffer, the trailing whitespace is stripped,
10705    // a newline is appended, and the edits provided by the language server
10706    // have been applied.
10707    format.await.unwrap();
10708    cx.assert_editor_state(
10709        &[
10710            "one",   //
10711            "",      //
10712            "twoˇ",  //
10713            "",      //
10714            "three", //
10715            "four",  //
10716            "",      //
10717        ]
10718        .join("\n"),
10719    );
10720
10721    // Undoing the formatting undoes the trailing whitespace removal, the
10722    // trailing newline, and the LSP edits.
10723    cx.update_buffer(|buffer, cx| buffer.undo(cx));
10724    cx.assert_editor_state(
10725        &[
10726            "one ",   //
10727            "twoˇ",   //
10728            "three ", //
10729            "four",   //
10730        ]
10731        .join("\n"),
10732    );
10733}
10734
10735#[gpui::test]
10736async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
10737    cx: &mut TestAppContext,
10738) {
10739    init_test(cx, |_| {});
10740
10741    cx.update(|cx| {
10742        cx.update_global::<SettingsStore, _>(|settings, cx| {
10743            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10744                settings.auto_signature_help = Some(true);
10745            });
10746        });
10747    });
10748
10749    let mut cx = EditorLspTestContext::new_rust(
10750        lsp::ServerCapabilities {
10751            signature_help_provider: Some(lsp::SignatureHelpOptions {
10752                ..Default::default()
10753            }),
10754            ..Default::default()
10755        },
10756        cx,
10757    )
10758    .await;
10759
10760    let language = Language::new(
10761        LanguageConfig {
10762            name: "Rust".into(),
10763            brackets: BracketPairConfig {
10764                pairs: vec![
10765                    BracketPair {
10766                        start: "{".to_string(),
10767                        end: "}".to_string(),
10768                        close: true,
10769                        surround: true,
10770                        newline: true,
10771                    },
10772                    BracketPair {
10773                        start: "(".to_string(),
10774                        end: ")".to_string(),
10775                        close: true,
10776                        surround: true,
10777                        newline: true,
10778                    },
10779                    BracketPair {
10780                        start: "/*".to_string(),
10781                        end: " */".to_string(),
10782                        close: true,
10783                        surround: true,
10784                        newline: true,
10785                    },
10786                    BracketPair {
10787                        start: "[".to_string(),
10788                        end: "]".to_string(),
10789                        close: false,
10790                        surround: false,
10791                        newline: true,
10792                    },
10793                    BracketPair {
10794                        start: "\"".to_string(),
10795                        end: "\"".to_string(),
10796                        close: true,
10797                        surround: true,
10798                        newline: false,
10799                    },
10800                    BracketPair {
10801                        start: "<".to_string(),
10802                        end: ">".to_string(),
10803                        close: false,
10804                        surround: true,
10805                        newline: true,
10806                    },
10807                ],
10808                ..Default::default()
10809            },
10810            autoclose_before: "})]".to_string(),
10811            ..Default::default()
10812        },
10813        Some(tree_sitter_rust::LANGUAGE.into()),
10814    );
10815    let language = Arc::new(language);
10816
10817    cx.language_registry().add(language.clone());
10818    cx.update_buffer(|buffer, cx| {
10819        buffer.set_language(Some(language), cx);
10820    });
10821
10822    cx.set_state(
10823        &r#"
10824            fn main() {
10825                sampleˇ
10826            }
10827        "#
10828        .unindent(),
10829    );
10830
10831    cx.update_editor(|editor, window, cx| {
10832        editor.handle_input("(", window, cx);
10833    });
10834    cx.assert_editor_state(
10835        &"
10836            fn main() {
10837                sample(ˇ)
10838            }
10839        "
10840        .unindent(),
10841    );
10842
10843    let mocked_response = lsp::SignatureHelp {
10844        signatures: vec![lsp::SignatureInformation {
10845            label: "fn sample(param1: u8, param2: u8)".to_string(),
10846            documentation: None,
10847            parameters: Some(vec![
10848                lsp::ParameterInformation {
10849                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10850                    documentation: None,
10851                },
10852                lsp::ParameterInformation {
10853                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10854                    documentation: None,
10855                },
10856            ]),
10857            active_parameter: None,
10858        }],
10859        active_signature: Some(0),
10860        active_parameter: Some(0),
10861    };
10862    handle_signature_help_request(&mut cx, mocked_response).await;
10863
10864    cx.condition(|editor, _| editor.signature_help_state.is_shown())
10865        .await;
10866
10867    cx.editor(|editor, _, _| {
10868        let signature_help_state = editor.signature_help_state.popover().cloned();
10869        let signature = signature_help_state.unwrap();
10870        assert_eq!(
10871            signature.signatures[signature.current_signature].label,
10872            "fn sample(param1: u8, param2: u8)"
10873        );
10874    });
10875}
10876
10877#[gpui::test]
10878async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
10879    init_test(cx, |_| {});
10880
10881    cx.update(|cx| {
10882        cx.update_global::<SettingsStore, _>(|settings, cx| {
10883            settings.update_user_settings::<EditorSettings>(cx, |settings| {
10884                settings.auto_signature_help = Some(false);
10885                settings.show_signature_help_after_edits = Some(false);
10886            });
10887        });
10888    });
10889
10890    let mut cx = EditorLspTestContext::new_rust(
10891        lsp::ServerCapabilities {
10892            signature_help_provider: Some(lsp::SignatureHelpOptions {
10893                ..Default::default()
10894            }),
10895            ..Default::default()
10896        },
10897        cx,
10898    )
10899    .await;
10900
10901    let language = Language::new(
10902        LanguageConfig {
10903            name: "Rust".into(),
10904            brackets: BracketPairConfig {
10905                pairs: vec![
10906                    BracketPair {
10907                        start: "{".to_string(),
10908                        end: "}".to_string(),
10909                        close: true,
10910                        surround: true,
10911                        newline: true,
10912                    },
10913                    BracketPair {
10914                        start: "(".to_string(),
10915                        end: ")".to_string(),
10916                        close: true,
10917                        surround: true,
10918                        newline: true,
10919                    },
10920                    BracketPair {
10921                        start: "/*".to_string(),
10922                        end: " */".to_string(),
10923                        close: true,
10924                        surround: true,
10925                        newline: true,
10926                    },
10927                    BracketPair {
10928                        start: "[".to_string(),
10929                        end: "]".to_string(),
10930                        close: false,
10931                        surround: false,
10932                        newline: true,
10933                    },
10934                    BracketPair {
10935                        start: "\"".to_string(),
10936                        end: "\"".to_string(),
10937                        close: true,
10938                        surround: true,
10939                        newline: false,
10940                    },
10941                    BracketPair {
10942                        start: "<".to_string(),
10943                        end: ">".to_string(),
10944                        close: false,
10945                        surround: true,
10946                        newline: true,
10947                    },
10948                ],
10949                ..Default::default()
10950            },
10951            autoclose_before: "})]".to_string(),
10952            ..Default::default()
10953        },
10954        Some(tree_sitter_rust::LANGUAGE.into()),
10955    );
10956    let language = Arc::new(language);
10957
10958    cx.language_registry().add(language.clone());
10959    cx.update_buffer(|buffer, cx| {
10960        buffer.set_language(Some(language), cx);
10961    });
10962
10963    // Ensure that signature_help is not called when no signature help is enabled.
10964    cx.set_state(
10965        &r#"
10966            fn main() {
10967                sampleˇ
10968            }
10969        "#
10970        .unindent(),
10971    );
10972    cx.update_editor(|editor, window, cx| {
10973        editor.handle_input("(", window, cx);
10974    });
10975    cx.assert_editor_state(
10976        &"
10977            fn main() {
10978                sample(ˇ)
10979            }
10980        "
10981        .unindent(),
10982    );
10983    cx.editor(|editor, _, _| {
10984        assert!(editor.signature_help_state.task().is_none());
10985    });
10986
10987    let mocked_response = lsp::SignatureHelp {
10988        signatures: vec![lsp::SignatureInformation {
10989            label: "fn sample(param1: u8, param2: u8)".to_string(),
10990            documentation: None,
10991            parameters: Some(vec![
10992                lsp::ParameterInformation {
10993                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
10994                    documentation: None,
10995                },
10996                lsp::ParameterInformation {
10997                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
10998                    documentation: None,
10999                },
11000            ]),
11001            active_parameter: None,
11002        }],
11003        active_signature: Some(0),
11004        active_parameter: Some(0),
11005    };
11006
11007    // Ensure that signature_help is called when enabled afte edits
11008    cx.update(|_, cx| {
11009        cx.update_global::<SettingsStore, _>(|settings, cx| {
11010            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11011                settings.auto_signature_help = Some(false);
11012                settings.show_signature_help_after_edits = Some(true);
11013            });
11014        });
11015    });
11016    cx.set_state(
11017        &r#"
11018            fn main() {
11019                sampleˇ
11020            }
11021        "#
11022        .unindent(),
11023    );
11024    cx.update_editor(|editor, window, cx| {
11025        editor.handle_input("(", window, cx);
11026    });
11027    cx.assert_editor_state(
11028        &"
11029            fn main() {
11030                sample(ˇ)
11031            }
11032        "
11033        .unindent(),
11034    );
11035    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11036    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11037        .await;
11038    cx.update_editor(|editor, _, _| {
11039        let signature_help_state = editor.signature_help_state.popover().cloned();
11040        assert!(signature_help_state.is_some());
11041        let signature = signature_help_state.unwrap();
11042        assert_eq!(
11043            signature.signatures[signature.current_signature].label,
11044            "fn sample(param1: u8, param2: u8)"
11045        );
11046        editor.signature_help_state = SignatureHelpState::default();
11047    });
11048
11049    // Ensure that signature_help is called when auto signature help override is enabled
11050    cx.update(|_, cx| {
11051        cx.update_global::<SettingsStore, _>(|settings, cx| {
11052            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11053                settings.auto_signature_help = Some(true);
11054                settings.show_signature_help_after_edits = Some(false);
11055            });
11056        });
11057    });
11058    cx.set_state(
11059        &r#"
11060            fn main() {
11061                sampleˇ
11062            }
11063        "#
11064        .unindent(),
11065    );
11066    cx.update_editor(|editor, window, cx| {
11067        editor.handle_input("(", window, cx);
11068    });
11069    cx.assert_editor_state(
11070        &"
11071            fn main() {
11072                sample(ˇ)
11073            }
11074        "
11075        .unindent(),
11076    );
11077    handle_signature_help_request(&mut cx, mocked_response).await;
11078    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11079        .await;
11080    cx.editor(|editor, _, _| {
11081        let signature_help_state = editor.signature_help_state.popover().cloned();
11082        assert!(signature_help_state.is_some());
11083        let signature = signature_help_state.unwrap();
11084        assert_eq!(
11085            signature.signatures[signature.current_signature].label,
11086            "fn sample(param1: u8, param2: u8)"
11087        );
11088    });
11089}
11090
11091#[gpui::test]
11092async fn test_signature_help(cx: &mut TestAppContext) {
11093    init_test(cx, |_| {});
11094    cx.update(|cx| {
11095        cx.update_global::<SettingsStore, _>(|settings, cx| {
11096            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11097                settings.auto_signature_help = Some(true);
11098            });
11099        });
11100    });
11101
11102    let mut cx = EditorLspTestContext::new_rust(
11103        lsp::ServerCapabilities {
11104            signature_help_provider: Some(lsp::SignatureHelpOptions {
11105                ..Default::default()
11106            }),
11107            ..Default::default()
11108        },
11109        cx,
11110    )
11111    .await;
11112
11113    // A test that directly calls `show_signature_help`
11114    cx.update_editor(|editor, window, cx| {
11115        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11116    });
11117
11118    let mocked_response = lsp::SignatureHelp {
11119        signatures: vec![lsp::SignatureInformation {
11120            label: "fn sample(param1: u8, param2: u8)".to_string(),
11121            documentation: None,
11122            parameters: Some(vec![
11123                lsp::ParameterInformation {
11124                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11125                    documentation: None,
11126                },
11127                lsp::ParameterInformation {
11128                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11129                    documentation: None,
11130                },
11131            ]),
11132            active_parameter: None,
11133        }],
11134        active_signature: Some(0),
11135        active_parameter: Some(0),
11136    };
11137    handle_signature_help_request(&mut cx, mocked_response).await;
11138
11139    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11140        .await;
11141
11142    cx.editor(|editor, _, _| {
11143        let signature_help_state = editor.signature_help_state.popover().cloned();
11144        assert!(signature_help_state.is_some());
11145        let signature = signature_help_state.unwrap();
11146        assert_eq!(
11147            signature.signatures[signature.current_signature].label,
11148            "fn sample(param1: u8, param2: u8)"
11149        );
11150    });
11151
11152    // When exiting outside from inside the brackets, `signature_help` is closed.
11153    cx.set_state(indoc! {"
11154        fn main() {
11155            sample(ˇ);
11156        }
11157
11158        fn sample(param1: u8, param2: u8) {}
11159    "});
11160
11161    cx.update_editor(|editor, window, cx| {
11162        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11163            s.select_ranges([0..0])
11164        });
11165    });
11166
11167    let mocked_response = lsp::SignatureHelp {
11168        signatures: Vec::new(),
11169        active_signature: None,
11170        active_parameter: None,
11171    };
11172    handle_signature_help_request(&mut cx, mocked_response).await;
11173
11174    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11175        .await;
11176
11177    cx.editor(|editor, _, _| {
11178        assert!(!editor.signature_help_state.is_shown());
11179    });
11180
11181    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11182    cx.set_state(indoc! {"
11183        fn main() {
11184            sample(ˇ);
11185        }
11186
11187        fn sample(param1: u8, param2: u8) {}
11188    "});
11189
11190    let mocked_response = lsp::SignatureHelp {
11191        signatures: vec![lsp::SignatureInformation {
11192            label: "fn sample(param1: u8, param2: u8)".to_string(),
11193            documentation: None,
11194            parameters: Some(vec![
11195                lsp::ParameterInformation {
11196                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11197                    documentation: None,
11198                },
11199                lsp::ParameterInformation {
11200                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11201                    documentation: None,
11202                },
11203            ]),
11204            active_parameter: None,
11205        }],
11206        active_signature: Some(0),
11207        active_parameter: Some(0),
11208    };
11209    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11210    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11211        .await;
11212    cx.editor(|editor, _, _| {
11213        assert!(editor.signature_help_state.is_shown());
11214    });
11215
11216    // Restore the popover with more parameter input
11217    cx.set_state(indoc! {"
11218        fn main() {
11219            sample(param1, param2ˇ);
11220        }
11221
11222        fn sample(param1: u8, param2: u8) {}
11223    "});
11224
11225    let mocked_response = lsp::SignatureHelp {
11226        signatures: vec![lsp::SignatureInformation {
11227            label: "fn sample(param1: u8, param2: u8)".to_string(),
11228            documentation: None,
11229            parameters: Some(vec![
11230                lsp::ParameterInformation {
11231                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11232                    documentation: None,
11233                },
11234                lsp::ParameterInformation {
11235                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11236                    documentation: None,
11237                },
11238            ]),
11239            active_parameter: None,
11240        }],
11241        active_signature: Some(0),
11242        active_parameter: Some(1),
11243    };
11244    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11245    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11246        .await;
11247
11248    // When selecting a range, the popover is gone.
11249    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11250    cx.update_editor(|editor, window, cx| {
11251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11252            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11253        })
11254    });
11255    cx.assert_editor_state(indoc! {"
11256        fn main() {
11257            sample(param1, «ˇparam2»);
11258        }
11259
11260        fn sample(param1: u8, param2: u8) {}
11261    "});
11262    cx.editor(|editor, _, _| {
11263        assert!(!editor.signature_help_state.is_shown());
11264    });
11265
11266    // When unselecting again, the popover is back if within the brackets.
11267    cx.update_editor(|editor, window, cx| {
11268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11269            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11270        })
11271    });
11272    cx.assert_editor_state(indoc! {"
11273        fn main() {
11274            sample(param1, ˇparam2);
11275        }
11276
11277        fn sample(param1: u8, param2: u8) {}
11278    "});
11279    handle_signature_help_request(&mut cx, mocked_response).await;
11280    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11281        .await;
11282    cx.editor(|editor, _, _| {
11283        assert!(editor.signature_help_state.is_shown());
11284    });
11285
11286    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11287    cx.update_editor(|editor, window, cx| {
11288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11289            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11290            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11291        })
11292    });
11293    cx.assert_editor_state(indoc! {"
11294        fn main() {
11295            sample(param1, ˇparam2);
11296        }
11297
11298        fn sample(param1: u8, param2: u8) {}
11299    "});
11300
11301    let mocked_response = lsp::SignatureHelp {
11302        signatures: vec![lsp::SignatureInformation {
11303            label: "fn sample(param1: u8, param2: u8)".to_string(),
11304            documentation: None,
11305            parameters: Some(vec![
11306                lsp::ParameterInformation {
11307                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11308                    documentation: None,
11309                },
11310                lsp::ParameterInformation {
11311                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11312                    documentation: None,
11313                },
11314            ]),
11315            active_parameter: None,
11316        }],
11317        active_signature: Some(0),
11318        active_parameter: Some(1),
11319    };
11320    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11321    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11322        .await;
11323    cx.update_editor(|editor, _, cx| {
11324        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11325    });
11326    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11327        .await;
11328    cx.update_editor(|editor, window, cx| {
11329        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11330            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11331        })
11332    });
11333    cx.assert_editor_state(indoc! {"
11334        fn main() {
11335            sample(param1, «ˇparam2»);
11336        }
11337
11338        fn sample(param1: u8, param2: u8) {}
11339    "});
11340    cx.update_editor(|editor, window, cx| {
11341        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11342            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11343        })
11344    });
11345    cx.assert_editor_state(indoc! {"
11346        fn main() {
11347            sample(param1, ˇparam2);
11348        }
11349
11350        fn sample(param1: u8, param2: u8) {}
11351    "});
11352    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11353        .await;
11354}
11355
11356#[gpui::test]
11357async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11358    init_test(cx, |_| {});
11359
11360    let mut cx = EditorLspTestContext::new_rust(
11361        lsp::ServerCapabilities {
11362            signature_help_provider: Some(lsp::SignatureHelpOptions {
11363                ..Default::default()
11364            }),
11365            ..Default::default()
11366        },
11367        cx,
11368    )
11369    .await;
11370
11371    cx.set_state(indoc! {"
11372        fn main() {
11373            overloadedˇ
11374        }
11375    "});
11376
11377    cx.update_editor(|editor, window, cx| {
11378        editor.handle_input("(", window, cx);
11379        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11380    });
11381
11382    // Mock response with 3 signatures
11383    let mocked_response = lsp::SignatureHelp {
11384        signatures: vec![
11385            lsp::SignatureInformation {
11386                label: "fn overloaded(x: i32)".to_string(),
11387                documentation: None,
11388                parameters: Some(vec![lsp::ParameterInformation {
11389                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11390                    documentation: None,
11391                }]),
11392                active_parameter: None,
11393            },
11394            lsp::SignatureInformation {
11395                label: "fn overloaded(x: i32, y: i32)".to_string(),
11396                documentation: None,
11397                parameters: Some(vec![
11398                    lsp::ParameterInformation {
11399                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11400                        documentation: None,
11401                    },
11402                    lsp::ParameterInformation {
11403                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11404                        documentation: None,
11405                    },
11406                ]),
11407                active_parameter: None,
11408            },
11409            lsp::SignatureInformation {
11410                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11411                documentation: None,
11412                parameters: Some(vec![
11413                    lsp::ParameterInformation {
11414                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11415                        documentation: None,
11416                    },
11417                    lsp::ParameterInformation {
11418                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11419                        documentation: None,
11420                    },
11421                    lsp::ParameterInformation {
11422                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11423                        documentation: None,
11424                    },
11425                ]),
11426                active_parameter: None,
11427            },
11428        ],
11429        active_signature: Some(1),
11430        active_parameter: Some(0),
11431    };
11432    handle_signature_help_request(&mut cx, mocked_response).await;
11433
11434    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11435        .await;
11436
11437    // Verify we have multiple signatures and the right one is selected
11438    cx.editor(|editor, _, _| {
11439        let popover = editor.signature_help_state.popover().cloned().unwrap();
11440        assert_eq!(popover.signatures.len(), 3);
11441        // active_signature was 1, so that should be the current
11442        assert_eq!(popover.current_signature, 1);
11443        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11444        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11445        assert_eq!(
11446            popover.signatures[2].label,
11447            "fn overloaded(x: i32, y: i32, z: i32)"
11448        );
11449    });
11450
11451    // Test navigation functionality
11452    cx.update_editor(|editor, window, cx| {
11453        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11454    });
11455
11456    cx.editor(|editor, _, _| {
11457        let popover = editor.signature_help_state.popover().cloned().unwrap();
11458        assert_eq!(popover.current_signature, 2);
11459    });
11460
11461    // Test wrap around
11462    cx.update_editor(|editor, window, cx| {
11463        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
11464    });
11465
11466    cx.editor(|editor, _, _| {
11467        let popover = editor.signature_help_state.popover().cloned().unwrap();
11468        assert_eq!(popover.current_signature, 0);
11469    });
11470
11471    // Test previous navigation
11472    cx.update_editor(|editor, window, cx| {
11473        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
11474    });
11475
11476    cx.editor(|editor, _, _| {
11477        let popover = editor.signature_help_state.popover().cloned().unwrap();
11478        assert_eq!(popover.current_signature, 2);
11479    });
11480}
11481
11482#[gpui::test]
11483async fn test_completion_mode(cx: &mut TestAppContext) {
11484    init_test(cx, |_| {});
11485    let mut cx = EditorLspTestContext::new_rust(
11486        lsp::ServerCapabilities {
11487            completion_provider: Some(lsp::CompletionOptions {
11488                resolve_provider: Some(true),
11489                ..Default::default()
11490            }),
11491            ..Default::default()
11492        },
11493        cx,
11494    )
11495    .await;
11496
11497    struct Run {
11498        run_description: &'static str,
11499        initial_state: String,
11500        buffer_marked_text: String,
11501        completion_label: &'static str,
11502        completion_text: &'static str,
11503        expected_with_insert_mode: String,
11504        expected_with_replace_mode: String,
11505        expected_with_replace_subsequence_mode: String,
11506        expected_with_replace_suffix_mode: String,
11507    }
11508
11509    let runs = [
11510        Run {
11511            run_description: "Start of word matches completion text",
11512            initial_state: "before ediˇ after".into(),
11513            buffer_marked_text: "before <edi|> after".into(),
11514            completion_label: "editor",
11515            completion_text: "editor",
11516            expected_with_insert_mode: "before editorˇ after".into(),
11517            expected_with_replace_mode: "before editorˇ after".into(),
11518            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11519            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11520        },
11521        Run {
11522            run_description: "Accept same text at the middle of the word",
11523            initial_state: "before ediˇtor after".into(),
11524            buffer_marked_text: "before <edi|tor> after".into(),
11525            completion_label: "editor",
11526            completion_text: "editor",
11527            expected_with_insert_mode: "before editorˇtor after".into(),
11528            expected_with_replace_mode: "before editorˇ after".into(),
11529            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11530            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11531        },
11532        Run {
11533            run_description: "End of word matches completion text -- cursor at end",
11534            initial_state: "before torˇ after".into(),
11535            buffer_marked_text: "before <tor|> after".into(),
11536            completion_label: "editor",
11537            completion_text: "editor",
11538            expected_with_insert_mode: "before editorˇ after".into(),
11539            expected_with_replace_mode: "before editorˇ after".into(),
11540            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11541            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11542        },
11543        Run {
11544            run_description: "End of word matches completion text -- cursor at start",
11545            initial_state: "before ˇtor after".into(),
11546            buffer_marked_text: "before <|tor> after".into(),
11547            completion_label: "editor",
11548            completion_text: "editor",
11549            expected_with_insert_mode: "before editorˇtor after".into(),
11550            expected_with_replace_mode: "before editorˇ after".into(),
11551            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11552            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11553        },
11554        Run {
11555            run_description: "Prepend text containing whitespace",
11556            initial_state: "pˇfield: bool".into(),
11557            buffer_marked_text: "<p|field>: bool".into(),
11558            completion_label: "pub ",
11559            completion_text: "pub ",
11560            expected_with_insert_mode: "pub ˇfield: bool".into(),
11561            expected_with_replace_mode: "pub ˇ: bool".into(),
11562            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
11563            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
11564        },
11565        Run {
11566            run_description: "Add element to start of list",
11567            initial_state: "[element_ˇelement_2]".into(),
11568            buffer_marked_text: "[<element_|element_2>]".into(),
11569            completion_label: "element_1",
11570            completion_text: "element_1",
11571            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
11572            expected_with_replace_mode: "[element_1ˇ]".into(),
11573            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
11574            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
11575        },
11576        Run {
11577            run_description: "Add element to start of list -- first and second elements are equal",
11578            initial_state: "[elˇelement]".into(),
11579            buffer_marked_text: "[<el|element>]".into(),
11580            completion_label: "element",
11581            completion_text: "element",
11582            expected_with_insert_mode: "[elementˇelement]".into(),
11583            expected_with_replace_mode: "[elementˇ]".into(),
11584            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
11585            expected_with_replace_suffix_mode: "[elementˇ]".into(),
11586        },
11587        Run {
11588            run_description: "Ends with matching suffix",
11589            initial_state: "SubˇError".into(),
11590            buffer_marked_text: "<Sub|Error>".into(),
11591            completion_label: "SubscriptionError",
11592            completion_text: "SubscriptionError",
11593            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
11594            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11595            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11596            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
11597        },
11598        Run {
11599            run_description: "Suffix is a subsequence -- contiguous",
11600            initial_state: "SubˇErr".into(),
11601            buffer_marked_text: "<Sub|Err>".into(),
11602            completion_label: "SubscriptionError",
11603            completion_text: "SubscriptionError",
11604            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
11605            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11606            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11607            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
11608        },
11609        Run {
11610            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
11611            initial_state: "Suˇscrirr".into(),
11612            buffer_marked_text: "<Su|scrirr>".into(),
11613            completion_label: "SubscriptionError",
11614            completion_text: "SubscriptionError",
11615            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
11616            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
11617            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
11618            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
11619        },
11620        Run {
11621            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
11622            initial_state: "foo(indˇix)".into(),
11623            buffer_marked_text: "foo(<ind|ix>)".into(),
11624            completion_label: "node_index",
11625            completion_text: "node_index",
11626            expected_with_insert_mode: "foo(node_indexˇix)".into(),
11627            expected_with_replace_mode: "foo(node_indexˇ)".into(),
11628            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
11629            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
11630        },
11631        Run {
11632            run_description: "Replace range ends before cursor - should extend to cursor",
11633            initial_state: "before editˇo after".into(),
11634            buffer_marked_text: "before <{ed}>it|o after".into(),
11635            completion_label: "editor",
11636            completion_text: "editor",
11637            expected_with_insert_mode: "before editorˇo after".into(),
11638            expected_with_replace_mode: "before editorˇo after".into(),
11639            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
11640            expected_with_replace_suffix_mode: "before editorˇo after".into(),
11641        },
11642        Run {
11643            run_description: "Uses label for suffix matching",
11644            initial_state: "before ediˇtor after".into(),
11645            buffer_marked_text: "before <edi|tor> after".into(),
11646            completion_label: "editor",
11647            completion_text: "editor()",
11648            expected_with_insert_mode: "before editor()ˇtor after".into(),
11649            expected_with_replace_mode: "before editor()ˇ after".into(),
11650            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
11651            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
11652        },
11653        Run {
11654            run_description: "Case insensitive subsequence and suffix matching",
11655            initial_state: "before EDiˇtoR after".into(),
11656            buffer_marked_text: "before <EDi|toR> after".into(),
11657            completion_label: "editor",
11658            completion_text: "editor",
11659            expected_with_insert_mode: "before editorˇtoR after".into(),
11660            expected_with_replace_mode: "before editorˇ after".into(),
11661            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
11662            expected_with_replace_suffix_mode: "before editorˇ after".into(),
11663        },
11664    ];
11665
11666    for run in runs {
11667        let run_variations = [
11668            (LspInsertMode::Insert, run.expected_with_insert_mode),
11669            (LspInsertMode::Replace, run.expected_with_replace_mode),
11670            (
11671                LspInsertMode::ReplaceSubsequence,
11672                run.expected_with_replace_subsequence_mode,
11673            ),
11674            (
11675                LspInsertMode::ReplaceSuffix,
11676                run.expected_with_replace_suffix_mode,
11677            ),
11678        ];
11679
11680        for (lsp_insert_mode, expected_text) in run_variations {
11681            eprintln!(
11682                "run = {:?}, mode = {lsp_insert_mode:.?}",
11683                run.run_description,
11684            );
11685
11686            update_test_language_settings(&mut cx, |settings| {
11687                settings.defaults.completions = Some(CompletionSettings {
11688                    lsp_insert_mode,
11689                    words: WordsCompletionMode::Disabled,
11690                    lsp: true,
11691                    lsp_fetch_timeout_ms: 0,
11692                });
11693            });
11694
11695            cx.set_state(&run.initial_state);
11696            cx.update_editor(|editor, window, cx| {
11697                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11698            });
11699
11700            let counter = Arc::new(AtomicUsize::new(0));
11701            handle_completion_request_with_insert_and_replace(
11702                &mut cx,
11703                &run.buffer_marked_text,
11704                vec![(run.completion_label, run.completion_text)],
11705                counter.clone(),
11706            )
11707            .await;
11708            cx.condition(|editor, _| editor.context_menu_visible())
11709                .await;
11710            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11711
11712            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11713                editor
11714                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
11715                    .unwrap()
11716            });
11717            cx.assert_editor_state(&expected_text);
11718            handle_resolve_completion_request(&mut cx, None).await;
11719            apply_additional_edits.await.unwrap();
11720        }
11721    }
11722}
11723
11724#[gpui::test]
11725async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
11726    init_test(cx, |_| {});
11727    let mut cx = EditorLspTestContext::new_rust(
11728        lsp::ServerCapabilities {
11729            completion_provider: Some(lsp::CompletionOptions {
11730                resolve_provider: Some(true),
11731                ..Default::default()
11732            }),
11733            ..Default::default()
11734        },
11735        cx,
11736    )
11737    .await;
11738
11739    let initial_state = "SubˇError";
11740    let buffer_marked_text = "<Sub|Error>";
11741    let completion_text = "SubscriptionError";
11742    let expected_with_insert_mode = "SubscriptionErrorˇError";
11743    let expected_with_replace_mode = "SubscriptionErrorˇ";
11744
11745    update_test_language_settings(&mut cx, |settings| {
11746        settings.defaults.completions = Some(CompletionSettings {
11747            words: WordsCompletionMode::Disabled,
11748            // set the opposite here to ensure that the action is overriding the default behavior
11749            lsp_insert_mode: LspInsertMode::Insert,
11750            lsp: true,
11751            lsp_fetch_timeout_ms: 0,
11752        });
11753    });
11754
11755    cx.set_state(initial_state);
11756    cx.update_editor(|editor, window, cx| {
11757        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11758    });
11759
11760    let counter = Arc::new(AtomicUsize::new(0));
11761    handle_completion_request_with_insert_and_replace(
11762        &mut cx,
11763        &buffer_marked_text,
11764        vec![(completion_text, completion_text)],
11765        counter.clone(),
11766    )
11767    .await;
11768    cx.condition(|editor, _| editor.context_menu_visible())
11769        .await;
11770    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
11771
11772    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11773        editor
11774            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11775            .unwrap()
11776    });
11777    cx.assert_editor_state(&expected_with_replace_mode);
11778    handle_resolve_completion_request(&mut cx, None).await;
11779    apply_additional_edits.await.unwrap();
11780
11781    update_test_language_settings(&mut cx, |settings| {
11782        settings.defaults.completions = Some(CompletionSettings {
11783            words: WordsCompletionMode::Disabled,
11784            // set the opposite here to ensure that the action is overriding the default behavior
11785            lsp_insert_mode: LspInsertMode::Replace,
11786            lsp: true,
11787            lsp_fetch_timeout_ms: 0,
11788        });
11789    });
11790
11791    cx.set_state(initial_state);
11792    cx.update_editor(|editor, window, cx| {
11793        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11794    });
11795    handle_completion_request_with_insert_and_replace(
11796        &mut cx,
11797        &buffer_marked_text,
11798        vec![(completion_text, completion_text)],
11799        counter.clone(),
11800    )
11801    .await;
11802    cx.condition(|editor, _| editor.context_menu_visible())
11803        .await;
11804    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
11805
11806    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11807        editor
11808            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
11809            .unwrap()
11810    });
11811    cx.assert_editor_state(&expected_with_insert_mode);
11812    handle_resolve_completion_request(&mut cx, None).await;
11813    apply_additional_edits.await.unwrap();
11814}
11815
11816#[gpui::test]
11817async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
11818    init_test(cx, |_| {});
11819    let mut cx = EditorLspTestContext::new_rust(
11820        lsp::ServerCapabilities {
11821            completion_provider: Some(lsp::CompletionOptions {
11822                resolve_provider: Some(true),
11823                ..Default::default()
11824            }),
11825            ..Default::default()
11826        },
11827        cx,
11828    )
11829    .await;
11830
11831    // scenario: surrounding text matches completion text
11832    let completion_text = "to_offset";
11833    let initial_state = indoc! {"
11834        1. buf.to_offˇsuffix
11835        2. buf.to_offˇsuf
11836        3. buf.to_offˇfix
11837        4. buf.to_offˇ
11838        5. into_offˇensive
11839        6. ˇsuffix
11840        7. let ˇ //
11841        8. aaˇzz
11842        9. buf.to_off«zzzzzˇ»suffix
11843        10. buf.«ˇzzzzz»suffix
11844        11. to_off«ˇzzzzz»
11845
11846        buf.to_offˇsuffix  // newest cursor
11847    "};
11848    let completion_marked_buffer = indoc! {"
11849        1. buf.to_offsuffix
11850        2. buf.to_offsuf
11851        3. buf.to_offfix
11852        4. buf.to_off
11853        5. into_offensive
11854        6. suffix
11855        7. let  //
11856        8. aazz
11857        9. buf.to_offzzzzzsuffix
11858        10. buf.zzzzzsuffix
11859        11. to_offzzzzz
11860
11861        buf.<to_off|suffix>  // newest cursor
11862    "};
11863    let expected = indoc! {"
11864        1. buf.to_offsetˇ
11865        2. buf.to_offsetˇsuf
11866        3. buf.to_offsetˇfix
11867        4. buf.to_offsetˇ
11868        5. into_offsetˇensive
11869        6. to_offsetˇsuffix
11870        7. let to_offsetˇ //
11871        8. aato_offsetˇzz
11872        9. buf.to_offsetˇ
11873        10. buf.to_offsetˇsuffix
11874        11. to_offsetˇ
11875
11876        buf.to_offsetˇ  // newest cursor
11877    "};
11878    cx.set_state(initial_state);
11879    cx.update_editor(|editor, window, cx| {
11880        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11881    });
11882    handle_completion_request_with_insert_and_replace(
11883        &mut cx,
11884        completion_marked_buffer,
11885        vec![(completion_text, completion_text)],
11886        Arc::new(AtomicUsize::new(0)),
11887    )
11888    .await;
11889    cx.condition(|editor, _| editor.context_menu_visible())
11890        .await;
11891    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11892        editor
11893            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11894            .unwrap()
11895    });
11896    cx.assert_editor_state(expected);
11897    handle_resolve_completion_request(&mut cx, None).await;
11898    apply_additional_edits.await.unwrap();
11899
11900    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
11901    let completion_text = "foo_and_bar";
11902    let initial_state = indoc! {"
11903        1. ooanbˇ
11904        2. zooanbˇ
11905        3. ooanbˇz
11906        4. zooanbˇz
11907        5. ooanˇ
11908        6. oanbˇ
11909
11910        ooanbˇ
11911    "};
11912    let completion_marked_buffer = indoc! {"
11913        1. ooanb
11914        2. zooanb
11915        3. ooanbz
11916        4. zooanbz
11917        5. ooan
11918        6. oanb
11919
11920        <ooanb|>
11921    "};
11922    let expected = indoc! {"
11923        1. foo_and_barˇ
11924        2. zfoo_and_barˇ
11925        3. foo_and_barˇz
11926        4. zfoo_and_barˇz
11927        5. ooanfoo_and_barˇ
11928        6. oanbfoo_and_barˇ
11929
11930        foo_and_barˇ
11931    "};
11932    cx.set_state(initial_state);
11933    cx.update_editor(|editor, window, cx| {
11934        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11935    });
11936    handle_completion_request_with_insert_and_replace(
11937        &mut cx,
11938        completion_marked_buffer,
11939        vec![(completion_text, completion_text)],
11940        Arc::new(AtomicUsize::new(0)),
11941    )
11942    .await;
11943    cx.condition(|editor, _| editor.context_menu_visible())
11944        .await;
11945    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11946        editor
11947            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11948            .unwrap()
11949    });
11950    cx.assert_editor_state(expected);
11951    handle_resolve_completion_request(&mut cx, None).await;
11952    apply_additional_edits.await.unwrap();
11953
11954    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
11955    // (expects the same as if it was inserted at the end)
11956    let completion_text = "foo_and_bar";
11957    let initial_state = indoc! {"
11958        1. ooˇanb
11959        2. zooˇanb
11960        3. ooˇanbz
11961        4. zooˇanbz
11962
11963        ooˇanb
11964    "};
11965    let completion_marked_buffer = indoc! {"
11966        1. ooanb
11967        2. zooanb
11968        3. ooanbz
11969        4. zooanbz
11970
11971        <oo|anb>
11972    "};
11973    let expected = indoc! {"
11974        1. foo_and_barˇ
11975        2. zfoo_and_barˇ
11976        3. foo_and_barˇz
11977        4. zfoo_and_barˇz
11978
11979        foo_and_barˇ
11980    "};
11981    cx.set_state(initial_state);
11982    cx.update_editor(|editor, window, cx| {
11983        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
11984    });
11985    handle_completion_request_with_insert_and_replace(
11986        &mut cx,
11987        completion_marked_buffer,
11988        vec![(completion_text, completion_text)],
11989        Arc::new(AtomicUsize::new(0)),
11990    )
11991    .await;
11992    cx.condition(|editor, _| editor.context_menu_visible())
11993        .await;
11994    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
11995        editor
11996            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
11997            .unwrap()
11998    });
11999    cx.assert_editor_state(expected);
12000    handle_resolve_completion_request(&mut cx, None).await;
12001    apply_additional_edits.await.unwrap();
12002}
12003
12004// This used to crash
12005#[gpui::test]
12006async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12007    init_test(cx, |_| {});
12008
12009    let buffer_text = indoc! {"
12010        fn main() {
12011            10.satu;
12012
12013            //
12014            // separate cursors so they open in different excerpts (manually reproducible)
12015            //
12016
12017            10.satu20;
12018        }
12019    "};
12020    let multibuffer_text_with_selections = indoc! {"
12021        fn main() {
12022            10.satuˇ;
12023
12024            //
12025
12026            //
12027
12028            10.satuˇ20;
12029        }
12030    "};
12031    let expected_multibuffer = indoc! {"
12032        fn main() {
12033            10.saturating_sub()ˇ;
12034
12035            //
12036
12037            //
12038
12039            10.saturating_sub()ˇ;
12040        }
12041    "};
12042
12043    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12044    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12045
12046    let fs = FakeFs::new(cx.executor());
12047    fs.insert_tree(
12048        path!("/a"),
12049        json!({
12050            "main.rs": buffer_text,
12051        }),
12052    )
12053    .await;
12054
12055    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12056    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12057    language_registry.add(rust_lang());
12058    let mut fake_servers = language_registry.register_fake_lsp(
12059        "Rust",
12060        FakeLspAdapter {
12061            capabilities: lsp::ServerCapabilities {
12062                completion_provider: Some(lsp::CompletionOptions {
12063                    resolve_provider: None,
12064                    ..lsp::CompletionOptions::default()
12065                }),
12066                ..lsp::ServerCapabilities::default()
12067            },
12068            ..FakeLspAdapter::default()
12069        },
12070    );
12071    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12072    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12073    let buffer = project
12074        .update(cx, |project, cx| {
12075            project.open_local_buffer(path!("/a/main.rs"), cx)
12076        })
12077        .await
12078        .unwrap();
12079
12080    let multi_buffer = cx.new(|cx| {
12081        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12082        multi_buffer.push_excerpts(
12083            buffer.clone(),
12084            [ExcerptRange::new(0..first_excerpt_end)],
12085            cx,
12086        );
12087        multi_buffer.push_excerpts(
12088            buffer.clone(),
12089            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12090            cx,
12091        );
12092        multi_buffer
12093    });
12094
12095    let editor = workspace
12096        .update(cx, |_, window, cx| {
12097            cx.new(|cx| {
12098                Editor::new(
12099                    EditorMode::Full {
12100                        scale_ui_elements_with_buffer_font_size: false,
12101                        show_active_line_background: false,
12102                        sized_by_content: false,
12103                    },
12104                    multi_buffer.clone(),
12105                    Some(project.clone()),
12106                    window,
12107                    cx,
12108                )
12109            })
12110        })
12111        .unwrap();
12112
12113    let pane = workspace
12114        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12115        .unwrap();
12116    pane.update_in(cx, |pane, window, cx| {
12117        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12118    });
12119
12120    let fake_server = fake_servers.next().await.unwrap();
12121
12122    editor.update_in(cx, |editor, window, cx| {
12123        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12124            s.select_ranges([
12125                Point::new(1, 11)..Point::new(1, 11),
12126                Point::new(7, 11)..Point::new(7, 11),
12127            ])
12128        });
12129
12130        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12131    });
12132
12133    editor.update_in(cx, |editor, window, cx| {
12134        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12135    });
12136
12137    fake_server
12138        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12139            let completion_item = lsp::CompletionItem {
12140                label: "saturating_sub()".into(),
12141                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12142                    lsp::InsertReplaceEdit {
12143                        new_text: "saturating_sub()".to_owned(),
12144                        insert: lsp::Range::new(
12145                            lsp::Position::new(7, 7),
12146                            lsp::Position::new(7, 11),
12147                        ),
12148                        replace: lsp::Range::new(
12149                            lsp::Position::new(7, 7),
12150                            lsp::Position::new(7, 13),
12151                        ),
12152                    },
12153                )),
12154                ..lsp::CompletionItem::default()
12155            };
12156
12157            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12158        })
12159        .next()
12160        .await
12161        .unwrap();
12162
12163    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12164        .await;
12165
12166    editor
12167        .update_in(cx, |editor, window, cx| {
12168            editor
12169                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12170                .unwrap()
12171        })
12172        .await
12173        .unwrap();
12174
12175    editor.update(cx, |editor, cx| {
12176        assert_text_with_selections(editor, expected_multibuffer, cx);
12177    })
12178}
12179
12180#[gpui::test]
12181async fn test_completion(cx: &mut TestAppContext) {
12182    init_test(cx, |_| {});
12183
12184    let mut cx = EditorLspTestContext::new_rust(
12185        lsp::ServerCapabilities {
12186            completion_provider: Some(lsp::CompletionOptions {
12187                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12188                resolve_provider: Some(true),
12189                ..Default::default()
12190            }),
12191            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12192            ..Default::default()
12193        },
12194        cx,
12195    )
12196    .await;
12197    let counter = Arc::new(AtomicUsize::new(0));
12198
12199    cx.set_state(indoc! {"
12200        oneˇ
12201        two
12202        three
12203    "});
12204    cx.simulate_keystroke(".");
12205    handle_completion_request(
12206        indoc! {"
12207            one.|<>
12208            two
12209            three
12210        "},
12211        vec!["first_completion", "second_completion"],
12212        true,
12213        counter.clone(),
12214        &mut cx,
12215    )
12216    .await;
12217    cx.condition(|editor, _| editor.context_menu_visible())
12218        .await;
12219    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12220
12221    let _handler = handle_signature_help_request(
12222        &mut cx,
12223        lsp::SignatureHelp {
12224            signatures: vec![lsp::SignatureInformation {
12225                label: "test signature".to_string(),
12226                documentation: None,
12227                parameters: Some(vec![lsp::ParameterInformation {
12228                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12229                    documentation: None,
12230                }]),
12231                active_parameter: None,
12232            }],
12233            active_signature: None,
12234            active_parameter: None,
12235        },
12236    );
12237    cx.update_editor(|editor, window, cx| {
12238        assert!(
12239            !editor.signature_help_state.is_shown(),
12240            "No signature help was called for"
12241        );
12242        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12243    });
12244    cx.run_until_parked();
12245    cx.update_editor(|editor, _, _| {
12246        assert!(
12247            !editor.signature_help_state.is_shown(),
12248            "No signature help should be shown when completions menu is open"
12249        );
12250    });
12251
12252    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12253        editor.context_menu_next(&Default::default(), window, cx);
12254        editor
12255            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12256            .unwrap()
12257    });
12258    cx.assert_editor_state(indoc! {"
12259        one.second_completionˇ
12260        two
12261        three
12262    "});
12263
12264    handle_resolve_completion_request(
12265        &mut cx,
12266        Some(vec![
12267            (
12268                //This overlaps with the primary completion edit which is
12269                //misbehavior from the LSP spec, test that we filter it out
12270                indoc! {"
12271                    one.second_ˇcompletion
12272                    two
12273                    threeˇ
12274                "},
12275                "overlapping additional edit",
12276            ),
12277            (
12278                indoc! {"
12279                    one.second_completion
12280                    two
12281                    threeˇ
12282                "},
12283                "\nadditional edit",
12284            ),
12285        ]),
12286    )
12287    .await;
12288    apply_additional_edits.await.unwrap();
12289    cx.assert_editor_state(indoc! {"
12290        one.second_completionˇ
12291        two
12292        three
12293        additional edit
12294    "});
12295
12296    cx.set_state(indoc! {"
12297        one.second_completion
12298        twoˇ
12299        threeˇ
12300        additional edit
12301    "});
12302    cx.simulate_keystroke(" ");
12303    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12304    cx.simulate_keystroke("s");
12305    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12306
12307    cx.assert_editor_state(indoc! {"
12308        one.second_completion
12309        two sˇ
12310        three sˇ
12311        additional edit
12312    "});
12313    handle_completion_request(
12314        indoc! {"
12315            one.second_completion
12316            two s
12317            three <s|>
12318            additional edit
12319        "},
12320        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12321        true,
12322        counter.clone(),
12323        &mut cx,
12324    )
12325    .await;
12326    cx.condition(|editor, _| editor.context_menu_visible())
12327        .await;
12328    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12329
12330    cx.simulate_keystroke("i");
12331
12332    handle_completion_request(
12333        indoc! {"
12334            one.second_completion
12335            two si
12336            three <si|>
12337            additional edit
12338        "},
12339        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12340        true,
12341        counter.clone(),
12342        &mut cx,
12343    )
12344    .await;
12345    cx.condition(|editor, _| editor.context_menu_visible())
12346        .await;
12347    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12348
12349    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12350        editor
12351            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12352            .unwrap()
12353    });
12354    cx.assert_editor_state(indoc! {"
12355        one.second_completion
12356        two sixth_completionˇ
12357        three sixth_completionˇ
12358        additional edit
12359    "});
12360
12361    apply_additional_edits.await.unwrap();
12362
12363    update_test_language_settings(&mut cx, |settings| {
12364        settings.defaults.show_completions_on_input = Some(false);
12365    });
12366    cx.set_state("editorˇ");
12367    cx.simulate_keystroke(".");
12368    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12369    cx.simulate_keystrokes("c l o");
12370    cx.assert_editor_state("editor.cloˇ");
12371    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12372    cx.update_editor(|editor, window, cx| {
12373        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12374    });
12375    handle_completion_request(
12376        "editor.<clo|>",
12377        vec!["close", "clobber"],
12378        true,
12379        counter.clone(),
12380        &mut cx,
12381    )
12382    .await;
12383    cx.condition(|editor, _| editor.context_menu_visible())
12384        .await;
12385    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12386
12387    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12388        editor
12389            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12390            .unwrap()
12391    });
12392    cx.assert_editor_state("editor.clobberˇ");
12393    handle_resolve_completion_request(&mut cx, None).await;
12394    apply_additional_edits.await.unwrap();
12395}
12396
12397#[gpui::test]
12398async fn test_completion_reuse(cx: &mut TestAppContext) {
12399    init_test(cx, |_| {});
12400
12401    let mut cx = EditorLspTestContext::new_rust(
12402        lsp::ServerCapabilities {
12403            completion_provider: Some(lsp::CompletionOptions {
12404                trigger_characters: Some(vec![".".to_string()]),
12405                ..Default::default()
12406            }),
12407            ..Default::default()
12408        },
12409        cx,
12410    )
12411    .await;
12412
12413    let counter = Arc::new(AtomicUsize::new(0));
12414    cx.set_state("objˇ");
12415    cx.simulate_keystroke(".");
12416
12417    // Initial completion request returns complete results
12418    let is_incomplete = false;
12419    handle_completion_request(
12420        "obj.|<>",
12421        vec!["a", "ab", "abc"],
12422        is_incomplete,
12423        counter.clone(),
12424        &mut cx,
12425    )
12426    .await;
12427    cx.run_until_parked();
12428    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12429    cx.assert_editor_state("obj.ˇ");
12430    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12431
12432    // Type "a" - filters existing completions
12433    cx.simulate_keystroke("a");
12434    cx.run_until_parked();
12435    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12436    cx.assert_editor_state("obj.aˇ");
12437    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12438
12439    // Type "b" - filters existing completions
12440    cx.simulate_keystroke("b");
12441    cx.run_until_parked();
12442    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12443    cx.assert_editor_state("obj.abˇ");
12444    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12445
12446    // Type "c" - filters existing completions
12447    cx.simulate_keystroke("c");
12448    cx.run_until_parked();
12449    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12450    cx.assert_editor_state("obj.abcˇ");
12451    check_displayed_completions(vec!["abc"], &mut cx);
12452
12453    // Backspace to delete "c" - filters existing completions
12454    cx.update_editor(|editor, window, cx| {
12455        editor.backspace(&Backspace, window, cx);
12456    });
12457    cx.run_until_parked();
12458    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12459    cx.assert_editor_state("obj.abˇ");
12460    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12461
12462    // Moving cursor to the left dismisses menu.
12463    cx.update_editor(|editor, window, cx| {
12464        editor.move_left(&MoveLeft, window, cx);
12465    });
12466    cx.run_until_parked();
12467    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12468    cx.assert_editor_state("obj.aˇb");
12469    cx.update_editor(|editor, _, _| {
12470        assert_eq!(editor.context_menu_visible(), false);
12471    });
12472
12473    // Type "b" - new request
12474    cx.simulate_keystroke("b");
12475    let is_incomplete = false;
12476    handle_completion_request(
12477        "obj.<ab|>a",
12478        vec!["ab", "abc"],
12479        is_incomplete,
12480        counter.clone(),
12481        &mut cx,
12482    )
12483    .await;
12484    cx.run_until_parked();
12485    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12486    cx.assert_editor_state("obj.abˇb");
12487    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12488
12489    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
12490    cx.update_editor(|editor, window, cx| {
12491        editor.backspace(&Backspace, window, cx);
12492    });
12493    let is_incomplete = false;
12494    handle_completion_request(
12495        "obj.<a|>b",
12496        vec!["a", "ab", "abc"],
12497        is_incomplete,
12498        counter.clone(),
12499        &mut cx,
12500    )
12501    .await;
12502    cx.run_until_parked();
12503    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12504    cx.assert_editor_state("obj.aˇb");
12505    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12506
12507    // Backspace to delete "a" - dismisses menu.
12508    cx.update_editor(|editor, window, cx| {
12509        editor.backspace(&Backspace, window, cx);
12510    });
12511    cx.run_until_parked();
12512    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12513    cx.assert_editor_state("obj.ˇb");
12514    cx.update_editor(|editor, _, _| {
12515        assert_eq!(editor.context_menu_visible(), false);
12516    });
12517}
12518
12519#[gpui::test]
12520async fn test_word_completion(cx: &mut TestAppContext) {
12521    let lsp_fetch_timeout_ms = 10;
12522    init_test(cx, |language_settings| {
12523        language_settings.defaults.completions = Some(CompletionSettings {
12524            words: WordsCompletionMode::Fallback,
12525            lsp: true,
12526            lsp_fetch_timeout_ms: 10,
12527            lsp_insert_mode: LspInsertMode::Insert,
12528        });
12529    });
12530
12531    let mut cx = EditorLspTestContext::new_rust(
12532        lsp::ServerCapabilities {
12533            completion_provider: Some(lsp::CompletionOptions {
12534                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12535                ..lsp::CompletionOptions::default()
12536            }),
12537            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12538            ..lsp::ServerCapabilities::default()
12539        },
12540        cx,
12541    )
12542    .await;
12543
12544    let throttle_completions = Arc::new(AtomicBool::new(false));
12545
12546    let lsp_throttle_completions = throttle_completions.clone();
12547    let _completion_requests_handler =
12548        cx.lsp
12549            .server
12550            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
12551                let lsp_throttle_completions = lsp_throttle_completions.clone();
12552                let cx = cx.clone();
12553                async move {
12554                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
12555                        cx.background_executor()
12556                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
12557                            .await;
12558                    }
12559                    Ok(Some(lsp::CompletionResponse::Array(vec![
12560                        lsp::CompletionItem {
12561                            label: "first".into(),
12562                            ..lsp::CompletionItem::default()
12563                        },
12564                        lsp::CompletionItem {
12565                            label: "last".into(),
12566                            ..lsp::CompletionItem::default()
12567                        },
12568                    ])))
12569                }
12570            });
12571
12572    cx.set_state(indoc! {"
12573        oneˇ
12574        two
12575        three
12576    "});
12577    cx.simulate_keystroke(".");
12578    cx.executor().run_until_parked();
12579    cx.condition(|editor, _| editor.context_menu_visible())
12580        .await;
12581    cx.update_editor(|editor, window, cx| {
12582        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12583        {
12584            assert_eq!(
12585                completion_menu_entries(&menu),
12586                &["first", "last"],
12587                "When LSP server is fast to reply, no fallback word completions are used"
12588            );
12589        } else {
12590            panic!("expected completion menu to be open");
12591        }
12592        editor.cancel(&Cancel, window, cx);
12593    });
12594    cx.executor().run_until_parked();
12595    cx.condition(|editor, _| !editor.context_menu_visible())
12596        .await;
12597
12598    throttle_completions.store(true, atomic::Ordering::Release);
12599    cx.simulate_keystroke(".");
12600    cx.executor()
12601        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
12602    cx.executor().run_until_parked();
12603    cx.condition(|editor, _| editor.context_menu_visible())
12604        .await;
12605    cx.update_editor(|editor, _, _| {
12606        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12607        {
12608            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
12609                "When LSP server is slow, document words can be shown instead, if configured accordingly");
12610        } else {
12611            panic!("expected completion menu to be open");
12612        }
12613    });
12614}
12615
12616#[gpui::test]
12617async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
12618    init_test(cx, |language_settings| {
12619        language_settings.defaults.completions = Some(CompletionSettings {
12620            words: WordsCompletionMode::Enabled,
12621            lsp: true,
12622            lsp_fetch_timeout_ms: 0,
12623            lsp_insert_mode: LspInsertMode::Insert,
12624        });
12625    });
12626
12627    let mut cx = EditorLspTestContext::new_rust(
12628        lsp::ServerCapabilities {
12629            completion_provider: Some(lsp::CompletionOptions {
12630                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12631                ..lsp::CompletionOptions::default()
12632            }),
12633            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12634            ..lsp::ServerCapabilities::default()
12635        },
12636        cx,
12637    )
12638    .await;
12639
12640    let _completion_requests_handler =
12641        cx.lsp
12642            .server
12643            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12644                Ok(Some(lsp::CompletionResponse::Array(vec![
12645                    lsp::CompletionItem {
12646                        label: "first".into(),
12647                        ..lsp::CompletionItem::default()
12648                    },
12649                    lsp::CompletionItem {
12650                        label: "last".into(),
12651                        ..lsp::CompletionItem::default()
12652                    },
12653                ])))
12654            });
12655
12656    cx.set_state(indoc! {"ˇ
12657        first
12658        last
12659        second
12660    "});
12661    cx.simulate_keystroke(".");
12662    cx.executor().run_until_parked();
12663    cx.condition(|editor, _| editor.context_menu_visible())
12664        .await;
12665    cx.update_editor(|editor, _, _| {
12666        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12667        {
12668            assert_eq!(
12669                completion_menu_entries(&menu),
12670                &["first", "last", "second"],
12671                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
12672            );
12673        } else {
12674            panic!("expected completion menu to be open");
12675        }
12676    });
12677}
12678
12679#[gpui::test]
12680async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
12681    init_test(cx, |language_settings| {
12682        language_settings.defaults.completions = Some(CompletionSettings {
12683            words: WordsCompletionMode::Disabled,
12684            lsp: true,
12685            lsp_fetch_timeout_ms: 0,
12686            lsp_insert_mode: LspInsertMode::Insert,
12687        });
12688    });
12689
12690    let mut cx = EditorLspTestContext::new_rust(
12691        lsp::ServerCapabilities {
12692            completion_provider: Some(lsp::CompletionOptions {
12693                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12694                ..lsp::CompletionOptions::default()
12695            }),
12696            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12697            ..lsp::ServerCapabilities::default()
12698        },
12699        cx,
12700    )
12701    .await;
12702
12703    let _completion_requests_handler =
12704        cx.lsp
12705            .server
12706            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12707                panic!("LSP completions should not be queried when dealing with word completions")
12708            });
12709
12710    cx.set_state(indoc! {"ˇ
12711        first
12712        last
12713        second
12714    "});
12715    cx.update_editor(|editor, window, cx| {
12716        editor.show_word_completions(&ShowWordCompletions, window, cx);
12717    });
12718    cx.executor().run_until_parked();
12719    cx.condition(|editor, _| editor.context_menu_visible())
12720        .await;
12721    cx.update_editor(|editor, _, _| {
12722        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12723        {
12724            assert_eq!(
12725                completion_menu_entries(&menu),
12726                &["first", "last", "second"],
12727                "`ShowWordCompletions` action should show word completions"
12728            );
12729        } else {
12730            panic!("expected completion menu to be open");
12731        }
12732    });
12733
12734    cx.simulate_keystroke("l");
12735    cx.executor().run_until_parked();
12736    cx.condition(|editor, _| editor.context_menu_visible())
12737        .await;
12738    cx.update_editor(|editor, _, _| {
12739        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12740        {
12741            assert_eq!(
12742                completion_menu_entries(&menu),
12743                &["last"],
12744                "After showing word completions, further editing should filter them and not query the LSP"
12745            );
12746        } else {
12747            panic!("expected completion menu to be open");
12748        }
12749    });
12750}
12751
12752#[gpui::test]
12753async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
12754    init_test(cx, |language_settings| {
12755        language_settings.defaults.completions = Some(CompletionSettings {
12756            words: WordsCompletionMode::Fallback,
12757            lsp: false,
12758            lsp_fetch_timeout_ms: 0,
12759            lsp_insert_mode: LspInsertMode::Insert,
12760        });
12761    });
12762
12763    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12764
12765    cx.set_state(indoc! {"ˇ
12766        0_usize
12767        let
12768        33
12769        4.5f32
12770    "});
12771    cx.update_editor(|editor, window, cx| {
12772        editor.show_completions(&ShowCompletions::default(), window, cx);
12773    });
12774    cx.executor().run_until_parked();
12775    cx.condition(|editor, _| editor.context_menu_visible())
12776        .await;
12777    cx.update_editor(|editor, window, cx| {
12778        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12779        {
12780            assert_eq!(
12781                completion_menu_entries(&menu),
12782                &["let"],
12783                "With no digits in the completion query, no digits should be in the word completions"
12784            );
12785        } else {
12786            panic!("expected completion menu to be open");
12787        }
12788        editor.cancel(&Cancel, window, cx);
12789    });
12790
12791    cx.set_state(indoc! {"12792        0_usize
12793        let
12794        3
12795        33.35f32
12796    "});
12797    cx.update_editor(|editor, window, cx| {
12798        editor.show_completions(&ShowCompletions::default(), window, cx);
12799    });
12800    cx.executor().run_until_parked();
12801    cx.condition(|editor, _| editor.context_menu_visible())
12802        .await;
12803    cx.update_editor(|editor, _, _| {
12804        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12805        {
12806            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
12807                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
12808        } else {
12809            panic!("expected completion menu to be open");
12810        }
12811    });
12812}
12813
12814fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
12815    let position = || lsp::Position {
12816        line: params.text_document_position.position.line,
12817        character: params.text_document_position.position.character,
12818    };
12819    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12820        range: lsp::Range {
12821            start: position(),
12822            end: position(),
12823        },
12824        new_text: text.to_string(),
12825    }))
12826}
12827
12828#[gpui::test]
12829async fn test_multiline_completion(cx: &mut TestAppContext) {
12830    init_test(cx, |_| {});
12831
12832    let fs = FakeFs::new(cx.executor());
12833    fs.insert_tree(
12834        path!("/a"),
12835        json!({
12836            "main.ts": "a",
12837        }),
12838    )
12839    .await;
12840
12841    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12842    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12843    let typescript_language = Arc::new(Language::new(
12844        LanguageConfig {
12845            name: "TypeScript".into(),
12846            matcher: LanguageMatcher {
12847                path_suffixes: vec!["ts".to_string()],
12848                ..LanguageMatcher::default()
12849            },
12850            line_comments: vec!["// ".into()],
12851            ..LanguageConfig::default()
12852        },
12853        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12854    ));
12855    language_registry.add(typescript_language.clone());
12856    let mut fake_servers = language_registry.register_fake_lsp(
12857        "TypeScript",
12858        FakeLspAdapter {
12859            capabilities: lsp::ServerCapabilities {
12860                completion_provider: Some(lsp::CompletionOptions {
12861                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12862                    ..lsp::CompletionOptions::default()
12863                }),
12864                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12865                ..lsp::ServerCapabilities::default()
12866            },
12867            // Emulate vtsls label generation
12868            label_for_completion: Some(Box::new(|item, _| {
12869                let text = if let Some(description) = item
12870                    .label_details
12871                    .as_ref()
12872                    .and_then(|label_details| label_details.description.as_ref())
12873                {
12874                    format!("{} {}", item.label, description)
12875                } else if let Some(detail) = &item.detail {
12876                    format!("{} {}", item.label, detail)
12877                } else {
12878                    item.label.clone()
12879                };
12880                let len = text.len();
12881                Some(language::CodeLabel {
12882                    text,
12883                    runs: Vec::new(),
12884                    filter_range: 0..len,
12885                })
12886            })),
12887            ..FakeLspAdapter::default()
12888        },
12889    );
12890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12891    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12892    let worktree_id = workspace
12893        .update(cx, |workspace, _window, cx| {
12894            workspace.project().update(cx, |project, cx| {
12895                project.worktrees(cx).next().unwrap().read(cx).id()
12896            })
12897        })
12898        .unwrap();
12899    let _buffer = project
12900        .update(cx, |project, cx| {
12901            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
12902        })
12903        .await
12904        .unwrap();
12905    let editor = workspace
12906        .update(cx, |workspace, window, cx| {
12907            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
12908        })
12909        .unwrap()
12910        .await
12911        .unwrap()
12912        .downcast::<Editor>()
12913        .unwrap();
12914    let fake_server = fake_servers.next().await.unwrap();
12915
12916    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
12917    let multiline_label_2 = "a\nb\nc\n";
12918    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
12919    let multiline_description = "d\ne\nf\n";
12920    let multiline_detail_2 = "g\nh\ni\n";
12921
12922    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
12923        move |params, _| async move {
12924            Ok(Some(lsp::CompletionResponse::Array(vec![
12925                lsp::CompletionItem {
12926                    label: multiline_label.to_string(),
12927                    text_edit: gen_text_edit(&params, "new_text_1"),
12928                    ..lsp::CompletionItem::default()
12929                },
12930                lsp::CompletionItem {
12931                    label: "single line label 1".to_string(),
12932                    detail: Some(multiline_detail.to_string()),
12933                    text_edit: gen_text_edit(&params, "new_text_2"),
12934                    ..lsp::CompletionItem::default()
12935                },
12936                lsp::CompletionItem {
12937                    label: "single line label 2".to_string(),
12938                    label_details: Some(lsp::CompletionItemLabelDetails {
12939                        description: Some(multiline_description.to_string()),
12940                        detail: None,
12941                    }),
12942                    text_edit: gen_text_edit(&params, "new_text_2"),
12943                    ..lsp::CompletionItem::default()
12944                },
12945                lsp::CompletionItem {
12946                    label: multiline_label_2.to_string(),
12947                    detail: Some(multiline_detail_2.to_string()),
12948                    text_edit: gen_text_edit(&params, "new_text_3"),
12949                    ..lsp::CompletionItem::default()
12950                },
12951                lsp::CompletionItem {
12952                    label: "Label with many     spaces and \t but without newlines".to_string(),
12953                    detail: Some(
12954                        "Details with many     spaces and \t but without newlines".to_string(),
12955                    ),
12956                    text_edit: gen_text_edit(&params, "new_text_4"),
12957                    ..lsp::CompletionItem::default()
12958                },
12959            ])))
12960        },
12961    );
12962
12963    editor.update_in(cx, |editor, window, cx| {
12964        cx.focus_self(window);
12965        editor.move_to_end(&MoveToEnd, window, cx);
12966        editor.handle_input(".", window, cx);
12967    });
12968    cx.run_until_parked();
12969    completion_handle.next().await.unwrap();
12970
12971    editor.update(cx, |editor, _| {
12972        assert!(editor.context_menu_visible());
12973        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12974        {
12975            let completion_labels = menu
12976                .completions
12977                .borrow()
12978                .iter()
12979                .map(|c| c.label.text.clone())
12980                .collect::<Vec<_>>();
12981            assert_eq!(
12982                completion_labels,
12983                &[
12984                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
12985                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
12986                    "single line label 2 d e f ",
12987                    "a b c g h i ",
12988                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
12989                ],
12990                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
12991            );
12992
12993            for completion in menu
12994                .completions
12995                .borrow()
12996                .iter() {
12997                    assert_eq!(
12998                        completion.label.filter_range,
12999                        0..completion.label.text.len(),
13000                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13001                    );
13002                }
13003        } else {
13004            panic!("expected completion menu to be open");
13005        }
13006    });
13007}
13008
13009#[gpui::test]
13010async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13011    init_test(cx, |_| {});
13012    let mut cx = EditorLspTestContext::new_rust(
13013        lsp::ServerCapabilities {
13014            completion_provider: Some(lsp::CompletionOptions {
13015                trigger_characters: Some(vec![".".to_string()]),
13016                ..Default::default()
13017            }),
13018            ..Default::default()
13019        },
13020        cx,
13021    )
13022    .await;
13023    cx.lsp
13024        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13025            Ok(Some(lsp::CompletionResponse::Array(vec![
13026                lsp::CompletionItem {
13027                    label: "first".into(),
13028                    ..Default::default()
13029                },
13030                lsp::CompletionItem {
13031                    label: "last".into(),
13032                    ..Default::default()
13033                },
13034            ])))
13035        });
13036    cx.set_state("variableˇ");
13037    cx.simulate_keystroke(".");
13038    cx.executor().run_until_parked();
13039
13040    cx.update_editor(|editor, _, _| {
13041        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13042        {
13043            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
13044        } else {
13045            panic!("expected completion menu to be open");
13046        }
13047    });
13048
13049    cx.update_editor(|editor, window, cx| {
13050        editor.move_page_down(&MovePageDown::default(), window, cx);
13051        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13052        {
13053            assert!(
13054                menu.selected_item == 1,
13055                "expected PageDown to select the last item from the context menu"
13056            );
13057        } else {
13058            panic!("expected completion menu to stay open after PageDown");
13059        }
13060    });
13061
13062    cx.update_editor(|editor, window, cx| {
13063        editor.move_page_up(&MovePageUp::default(), window, cx);
13064        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13065        {
13066            assert!(
13067                menu.selected_item == 0,
13068                "expected PageUp to select the first item from the context menu"
13069            );
13070        } else {
13071            panic!("expected completion menu to stay open after PageUp");
13072        }
13073    });
13074}
13075
13076#[gpui::test]
13077async fn test_as_is_completions(cx: &mut TestAppContext) {
13078    init_test(cx, |_| {});
13079    let mut cx = EditorLspTestContext::new_rust(
13080        lsp::ServerCapabilities {
13081            completion_provider: Some(lsp::CompletionOptions {
13082                ..Default::default()
13083            }),
13084            ..Default::default()
13085        },
13086        cx,
13087    )
13088    .await;
13089    cx.lsp
13090        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13091            Ok(Some(lsp::CompletionResponse::Array(vec![
13092                lsp::CompletionItem {
13093                    label: "unsafe".into(),
13094                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13095                        range: lsp::Range {
13096                            start: lsp::Position {
13097                                line: 1,
13098                                character: 2,
13099                            },
13100                            end: lsp::Position {
13101                                line: 1,
13102                                character: 3,
13103                            },
13104                        },
13105                        new_text: "unsafe".to_string(),
13106                    })),
13107                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13108                    ..Default::default()
13109                },
13110            ])))
13111        });
13112    cx.set_state("fn a() {}\n");
13113    cx.executor().run_until_parked();
13114    cx.update_editor(|editor, window, cx| {
13115        editor.show_completions(
13116            &ShowCompletions {
13117                trigger: Some("\n".into()),
13118            },
13119            window,
13120            cx,
13121        );
13122    });
13123    cx.executor().run_until_parked();
13124
13125    cx.update_editor(|editor, window, cx| {
13126        editor.confirm_completion(&Default::default(), window, cx)
13127    });
13128    cx.executor().run_until_parked();
13129    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13130}
13131
13132#[gpui::test]
13133async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13134    init_test(cx, |_| {});
13135
13136    let mut cx = EditorLspTestContext::new_rust(
13137        lsp::ServerCapabilities {
13138            completion_provider: Some(lsp::CompletionOptions {
13139                trigger_characters: Some(vec![".".to_string()]),
13140                resolve_provider: Some(true),
13141                ..Default::default()
13142            }),
13143            ..Default::default()
13144        },
13145        cx,
13146    )
13147    .await;
13148
13149    cx.set_state("fn main() { let a = 2ˇ; }");
13150    cx.simulate_keystroke(".");
13151    let completion_item = lsp::CompletionItem {
13152        label: "Some".into(),
13153        kind: Some(lsp::CompletionItemKind::SNIPPET),
13154        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13155        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13156            kind: lsp::MarkupKind::Markdown,
13157            value: "```rust\nSome(2)\n```".to_string(),
13158        })),
13159        deprecated: Some(false),
13160        sort_text: Some("Some".to_string()),
13161        filter_text: Some("Some".to_string()),
13162        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13163        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13164            range: lsp::Range {
13165                start: lsp::Position {
13166                    line: 0,
13167                    character: 22,
13168                },
13169                end: lsp::Position {
13170                    line: 0,
13171                    character: 22,
13172                },
13173            },
13174            new_text: "Some(2)".to_string(),
13175        })),
13176        additional_text_edits: Some(vec![lsp::TextEdit {
13177            range: lsp::Range {
13178                start: lsp::Position {
13179                    line: 0,
13180                    character: 20,
13181                },
13182                end: lsp::Position {
13183                    line: 0,
13184                    character: 22,
13185                },
13186            },
13187            new_text: "".to_string(),
13188        }]),
13189        ..Default::default()
13190    };
13191
13192    let closure_completion_item = completion_item.clone();
13193    let counter = Arc::new(AtomicUsize::new(0));
13194    let counter_clone = counter.clone();
13195    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13196        let task_completion_item = closure_completion_item.clone();
13197        counter_clone.fetch_add(1, atomic::Ordering::Release);
13198        async move {
13199            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13200                is_incomplete: true,
13201                item_defaults: None,
13202                items: vec![task_completion_item],
13203            })))
13204        }
13205    });
13206
13207    cx.condition(|editor, _| editor.context_menu_visible())
13208        .await;
13209    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13210    assert!(request.next().await.is_some());
13211    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13212
13213    cx.simulate_keystrokes("S o m");
13214    cx.condition(|editor, _| editor.context_menu_visible())
13215        .await;
13216    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13217    assert!(request.next().await.is_some());
13218    assert!(request.next().await.is_some());
13219    assert!(request.next().await.is_some());
13220    request.close();
13221    assert!(request.next().await.is_none());
13222    assert_eq!(
13223        counter.load(atomic::Ordering::Acquire),
13224        4,
13225        "With the completions menu open, only one LSP request should happen per input"
13226    );
13227}
13228
13229#[gpui::test]
13230async fn test_toggle_comment(cx: &mut TestAppContext) {
13231    init_test(cx, |_| {});
13232    let mut cx = EditorTestContext::new(cx).await;
13233    let language = Arc::new(Language::new(
13234        LanguageConfig {
13235            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13236            ..Default::default()
13237        },
13238        Some(tree_sitter_rust::LANGUAGE.into()),
13239    ));
13240    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13241
13242    // If multiple selections intersect a line, the line is only toggled once.
13243    cx.set_state(indoc! {"
13244        fn a() {
13245            «//b();
13246            ˇ»// «c();
13247            //ˇ»  d();
13248        }
13249    "});
13250
13251    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13252
13253    cx.assert_editor_state(indoc! {"
13254        fn a() {
13255            «b();
13256            c();
13257            ˇ» d();
13258        }
13259    "});
13260
13261    // The comment prefix is inserted at the same column for every line in a
13262    // selection.
13263    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13264
13265    cx.assert_editor_state(indoc! {"
13266        fn a() {
13267            // «b();
13268            // c();
13269            ˇ»//  d();
13270        }
13271    "});
13272
13273    // If a selection ends at the beginning of a line, that line is not toggled.
13274    cx.set_selections_state(indoc! {"
13275        fn a() {
13276            // b();
13277            «// c();
13278        ˇ»    //  d();
13279        }
13280    "});
13281
13282    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13283
13284    cx.assert_editor_state(indoc! {"
13285        fn a() {
13286            // b();
13287            «c();
13288        ˇ»    //  d();
13289        }
13290    "});
13291
13292    // If a selection span a single line and is empty, the line is toggled.
13293    cx.set_state(indoc! {"
13294        fn a() {
13295            a();
13296            b();
13297        ˇ
13298        }
13299    "});
13300
13301    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13302
13303    cx.assert_editor_state(indoc! {"
13304        fn a() {
13305            a();
13306            b();
13307        //•ˇ
13308        }
13309    "});
13310
13311    // If a selection span multiple lines, empty lines are not toggled.
13312    cx.set_state(indoc! {"
13313        fn a() {
13314            «a();
13315
13316            c();ˇ»
13317        }
13318    "});
13319
13320    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13321
13322    cx.assert_editor_state(indoc! {"
13323        fn a() {
13324            // «a();
13325
13326            // c();ˇ»
13327        }
13328    "});
13329
13330    // If a selection includes multiple comment prefixes, all lines are uncommented.
13331    cx.set_state(indoc! {"
13332        fn a() {
13333            «// a();
13334            /// b();
13335            //! c();ˇ»
13336        }
13337    "});
13338
13339    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
13340
13341    cx.assert_editor_state(indoc! {"
13342        fn a() {
13343            «a();
13344            b();
13345            c();ˇ»
13346        }
13347    "});
13348}
13349
13350#[gpui::test]
13351async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
13352    init_test(cx, |_| {});
13353    let mut cx = EditorTestContext::new(cx).await;
13354    let language = Arc::new(Language::new(
13355        LanguageConfig {
13356            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
13357            ..Default::default()
13358        },
13359        Some(tree_sitter_rust::LANGUAGE.into()),
13360    ));
13361    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
13362
13363    let toggle_comments = &ToggleComments {
13364        advance_downwards: false,
13365        ignore_indent: true,
13366    };
13367
13368    // If multiple selections intersect a line, the line is only toggled once.
13369    cx.set_state(indoc! {"
13370        fn a() {
13371        //    «b();
13372        //    c();
13373        //    ˇ» d();
13374        }
13375    "});
13376
13377    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13378
13379    cx.assert_editor_state(indoc! {"
13380        fn a() {
13381            «b();
13382            c();
13383            ˇ» d();
13384        }
13385    "});
13386
13387    // The comment prefix is inserted at the beginning of each line
13388    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13389
13390    cx.assert_editor_state(indoc! {"
13391        fn a() {
13392        //    «b();
13393        //    c();
13394        //    ˇ» d();
13395        }
13396    "});
13397
13398    // If a selection ends at the beginning of a line, that line is not toggled.
13399    cx.set_selections_state(indoc! {"
13400        fn a() {
13401        //    b();
13402        //    «c();
13403        ˇ»//     d();
13404        }
13405    "});
13406
13407    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13408
13409    cx.assert_editor_state(indoc! {"
13410        fn a() {
13411        //    b();
13412            «c();
13413        ˇ»//     d();
13414        }
13415    "});
13416
13417    // If a selection span a single line and is empty, the line is toggled.
13418    cx.set_state(indoc! {"
13419        fn a() {
13420            a();
13421            b();
13422        ˇ
13423        }
13424    "});
13425
13426    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13427
13428    cx.assert_editor_state(indoc! {"
13429        fn a() {
13430            a();
13431            b();
13432        //ˇ
13433        }
13434    "});
13435
13436    // If a selection span multiple lines, empty lines are not toggled.
13437    cx.set_state(indoc! {"
13438        fn a() {
13439            «a();
13440
13441            c();ˇ»
13442        }
13443    "});
13444
13445    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13446
13447    cx.assert_editor_state(indoc! {"
13448        fn a() {
13449        //    «a();
13450
13451        //    c();ˇ»
13452        }
13453    "});
13454
13455    // If a selection includes multiple comment prefixes, all lines are uncommented.
13456    cx.set_state(indoc! {"
13457        fn a() {
13458        //    «a();
13459        ///    b();
13460        //!    c();ˇ»
13461        }
13462    "});
13463
13464    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
13465
13466    cx.assert_editor_state(indoc! {"
13467        fn a() {
13468            «a();
13469            b();
13470            c();ˇ»
13471        }
13472    "});
13473}
13474
13475#[gpui::test]
13476async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
13477    init_test(cx, |_| {});
13478
13479    let language = Arc::new(Language::new(
13480        LanguageConfig {
13481            line_comments: vec!["// ".into()],
13482            ..Default::default()
13483        },
13484        Some(tree_sitter_rust::LANGUAGE.into()),
13485    ));
13486
13487    let mut cx = EditorTestContext::new(cx).await;
13488
13489    cx.language_registry().add(language.clone());
13490    cx.update_buffer(|buffer, cx| {
13491        buffer.set_language(Some(language), cx);
13492    });
13493
13494    let toggle_comments = &ToggleComments {
13495        advance_downwards: true,
13496        ignore_indent: false,
13497    };
13498
13499    // Single cursor on one line -> advance
13500    // Cursor moves horizontally 3 characters as well on non-blank line
13501    cx.set_state(indoc!(
13502        "fn a() {
13503             ˇdog();
13504             cat();
13505        }"
13506    ));
13507    cx.update_editor(|editor, window, cx| {
13508        editor.toggle_comments(toggle_comments, window, cx);
13509    });
13510    cx.assert_editor_state(indoc!(
13511        "fn a() {
13512             // dog();
13513             catˇ();
13514        }"
13515    ));
13516
13517    // Single selection on one line -> don't advance
13518    cx.set_state(indoc!(
13519        "fn a() {
13520             «dog()ˇ»;
13521             cat();
13522        }"
13523    ));
13524    cx.update_editor(|editor, window, cx| {
13525        editor.toggle_comments(toggle_comments, window, cx);
13526    });
13527    cx.assert_editor_state(indoc!(
13528        "fn a() {
13529             // «dog()ˇ»;
13530             cat();
13531        }"
13532    ));
13533
13534    // Multiple cursors on one line -> advance
13535    cx.set_state(indoc!(
13536        "fn a() {
13537             ˇdˇog();
13538             cat();
13539        }"
13540    ));
13541    cx.update_editor(|editor, window, cx| {
13542        editor.toggle_comments(toggle_comments, window, cx);
13543    });
13544    cx.assert_editor_state(indoc!(
13545        "fn a() {
13546             // dog();
13547             catˇ(ˇ);
13548        }"
13549    ));
13550
13551    // Multiple cursors on one line, with selection -> don't advance
13552    cx.set_state(indoc!(
13553        "fn a() {
13554             ˇdˇog«()ˇ»;
13555             cat();
13556        }"
13557    ));
13558    cx.update_editor(|editor, window, cx| {
13559        editor.toggle_comments(toggle_comments, window, cx);
13560    });
13561    cx.assert_editor_state(indoc!(
13562        "fn a() {
13563             // ˇdˇog«()ˇ»;
13564             cat();
13565        }"
13566    ));
13567
13568    // Single cursor on one line -> advance
13569    // Cursor moves to column 0 on blank line
13570    cx.set_state(indoc!(
13571        "fn a() {
13572             ˇdog();
13573
13574             cat();
13575        }"
13576    ));
13577    cx.update_editor(|editor, window, cx| {
13578        editor.toggle_comments(toggle_comments, window, cx);
13579    });
13580    cx.assert_editor_state(indoc!(
13581        "fn a() {
13582             // dog();
13583        ˇ
13584             cat();
13585        }"
13586    ));
13587
13588    // Single cursor on one line -> advance
13589    // Cursor starts and ends at column 0
13590    cx.set_state(indoc!(
13591        "fn a() {
13592         ˇ    dog();
13593             cat();
13594        }"
13595    ));
13596    cx.update_editor(|editor, window, cx| {
13597        editor.toggle_comments(toggle_comments, window, cx);
13598    });
13599    cx.assert_editor_state(indoc!(
13600        "fn a() {
13601             // dog();
13602         ˇ    cat();
13603        }"
13604    ));
13605}
13606
13607#[gpui::test]
13608async fn test_toggle_block_comment(cx: &mut TestAppContext) {
13609    init_test(cx, |_| {});
13610
13611    let mut cx = EditorTestContext::new(cx).await;
13612
13613    let html_language = Arc::new(
13614        Language::new(
13615            LanguageConfig {
13616                name: "HTML".into(),
13617                block_comment: Some(("<!-- ".into(), " -->".into())),
13618                ..Default::default()
13619            },
13620            Some(tree_sitter_html::LANGUAGE.into()),
13621        )
13622        .with_injection_query(
13623            r#"
13624            (script_element
13625                (raw_text) @injection.content
13626                (#set! injection.language "javascript"))
13627            "#,
13628        )
13629        .unwrap(),
13630    );
13631
13632    let javascript_language = Arc::new(Language::new(
13633        LanguageConfig {
13634            name: "JavaScript".into(),
13635            line_comments: vec!["// ".into()],
13636            ..Default::default()
13637        },
13638        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13639    ));
13640
13641    cx.language_registry().add(html_language.clone());
13642    cx.language_registry().add(javascript_language.clone());
13643    cx.update_buffer(|buffer, cx| {
13644        buffer.set_language(Some(html_language), cx);
13645    });
13646
13647    // Toggle comments for empty selections
13648    cx.set_state(
13649        &r#"
13650            <p>A</p>ˇ
13651            <p>B</p>ˇ
13652            <p>C</p>ˇ
13653        "#
13654        .unindent(),
13655    );
13656    cx.update_editor(|editor, window, cx| {
13657        editor.toggle_comments(&ToggleComments::default(), window, cx)
13658    });
13659    cx.assert_editor_state(
13660        &r#"
13661            <!-- <p>A</p>ˇ -->
13662            <!-- <p>B</p>ˇ -->
13663            <!-- <p>C</p>ˇ -->
13664        "#
13665        .unindent(),
13666    );
13667    cx.update_editor(|editor, window, cx| {
13668        editor.toggle_comments(&ToggleComments::default(), window, cx)
13669    });
13670    cx.assert_editor_state(
13671        &r#"
13672            <p>A</p>ˇ
13673            <p>B</p>ˇ
13674            <p>C</p>ˇ
13675        "#
13676        .unindent(),
13677    );
13678
13679    // Toggle comments for mixture of empty and non-empty selections, where
13680    // multiple selections occupy a given line.
13681    cx.set_state(
13682        &r#"
13683            <p>A«</p>
13684            <p>ˇ»B</p>ˇ
13685            <p>C«</p>
13686            <p>ˇ»D</p>ˇ
13687        "#
13688        .unindent(),
13689    );
13690
13691    cx.update_editor(|editor, window, cx| {
13692        editor.toggle_comments(&ToggleComments::default(), window, cx)
13693    });
13694    cx.assert_editor_state(
13695        &r#"
13696            <!-- <p>A«</p>
13697            <p>ˇ»B</p>ˇ -->
13698            <!-- <p>C«</p>
13699            <p>ˇ»D</p>ˇ -->
13700        "#
13701        .unindent(),
13702    );
13703    cx.update_editor(|editor, window, cx| {
13704        editor.toggle_comments(&ToggleComments::default(), window, cx)
13705    });
13706    cx.assert_editor_state(
13707        &r#"
13708            <p>A«</p>
13709            <p>ˇ»B</p>ˇ
13710            <p>C«</p>
13711            <p>ˇ»D</p>ˇ
13712        "#
13713        .unindent(),
13714    );
13715
13716    // Toggle comments when different languages are active for different
13717    // selections.
13718    cx.set_state(
13719        &r#"
13720            ˇ<script>
13721                ˇvar x = new Y();
13722            ˇ</script>
13723        "#
13724        .unindent(),
13725    );
13726    cx.executor().run_until_parked();
13727    cx.update_editor(|editor, window, cx| {
13728        editor.toggle_comments(&ToggleComments::default(), window, cx)
13729    });
13730    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
13731    // Uncommenting and commenting from this position brings in even more wrong artifacts.
13732    cx.assert_editor_state(
13733        &r#"
13734            <!-- ˇ<script> -->
13735                // ˇvar x = new Y();
13736            <!-- ˇ</script> -->
13737        "#
13738        .unindent(),
13739    );
13740}
13741
13742#[gpui::test]
13743fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
13744    init_test(cx, |_| {});
13745
13746    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13747    let multibuffer = cx.new(|cx| {
13748        let mut multibuffer = MultiBuffer::new(ReadWrite);
13749        multibuffer.push_excerpts(
13750            buffer.clone(),
13751            [
13752                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
13753                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
13754            ],
13755            cx,
13756        );
13757        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
13758        multibuffer
13759    });
13760
13761    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13762    editor.update_in(cx, |editor, window, cx| {
13763        assert_eq!(editor.text(cx), "aaaa\nbbbb");
13764        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13765            s.select_ranges([
13766                Point::new(0, 0)..Point::new(0, 0),
13767                Point::new(1, 0)..Point::new(1, 0),
13768            ])
13769        });
13770
13771        editor.handle_input("X", window, cx);
13772        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
13773        assert_eq!(
13774            editor.selections.ranges(cx),
13775            [
13776                Point::new(0, 1)..Point::new(0, 1),
13777                Point::new(1, 1)..Point::new(1, 1),
13778            ]
13779        );
13780
13781        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
13782        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13783            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
13784        });
13785        editor.backspace(&Default::default(), window, cx);
13786        assert_eq!(editor.text(cx), "Xa\nbbb");
13787        assert_eq!(
13788            editor.selections.ranges(cx),
13789            [Point::new(1, 0)..Point::new(1, 0)]
13790        );
13791
13792        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13793            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
13794        });
13795        editor.backspace(&Default::default(), window, cx);
13796        assert_eq!(editor.text(cx), "X\nbb");
13797        assert_eq!(
13798            editor.selections.ranges(cx),
13799            [Point::new(0, 1)..Point::new(0, 1)]
13800        );
13801    });
13802}
13803
13804#[gpui::test]
13805fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
13806    init_test(cx, |_| {});
13807
13808    let markers = vec![('[', ']').into(), ('(', ')').into()];
13809    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
13810        indoc! {"
13811            [aaaa
13812            (bbbb]
13813            cccc)",
13814        },
13815        markers.clone(),
13816    );
13817    let excerpt_ranges = markers.into_iter().map(|marker| {
13818        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
13819        ExcerptRange::new(context.clone())
13820    });
13821    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
13822    let multibuffer = cx.new(|cx| {
13823        let mut multibuffer = MultiBuffer::new(ReadWrite);
13824        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
13825        multibuffer
13826    });
13827
13828    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
13829    editor.update_in(cx, |editor, window, cx| {
13830        let (expected_text, selection_ranges) = marked_text_ranges(
13831            indoc! {"
13832                aaaa
13833                bˇbbb
13834                bˇbbˇb
13835                cccc"
13836            },
13837            true,
13838        );
13839        assert_eq!(editor.text(cx), expected_text);
13840        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13841            s.select_ranges(selection_ranges)
13842        });
13843
13844        editor.handle_input("X", window, cx);
13845
13846        let (expected_text, expected_selections) = marked_text_ranges(
13847            indoc! {"
13848                aaaa
13849                bXˇbbXb
13850                bXˇbbXˇb
13851                cccc"
13852            },
13853            false,
13854        );
13855        assert_eq!(editor.text(cx), expected_text);
13856        assert_eq!(editor.selections.ranges(cx), expected_selections);
13857
13858        editor.newline(&Newline, window, cx);
13859        let (expected_text, expected_selections) = marked_text_ranges(
13860            indoc! {"
13861                aaaa
13862                bX
13863                ˇbbX
13864                b
13865                bX
13866                ˇbbX
13867                ˇb
13868                cccc"
13869            },
13870            false,
13871        );
13872        assert_eq!(editor.text(cx), expected_text);
13873        assert_eq!(editor.selections.ranges(cx), expected_selections);
13874    });
13875}
13876
13877#[gpui::test]
13878fn test_refresh_selections(cx: &mut TestAppContext) {
13879    init_test(cx, |_| {});
13880
13881    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13882    let mut excerpt1_id = None;
13883    let multibuffer = cx.new(|cx| {
13884        let mut multibuffer = MultiBuffer::new(ReadWrite);
13885        excerpt1_id = multibuffer
13886            .push_excerpts(
13887                buffer.clone(),
13888                [
13889                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13890                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13891                ],
13892                cx,
13893            )
13894            .into_iter()
13895            .next();
13896        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13897        multibuffer
13898    });
13899
13900    let editor = cx.add_window(|window, cx| {
13901        let mut editor = build_editor(multibuffer.clone(), window, cx);
13902        let snapshot = editor.snapshot(window, cx);
13903        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13904            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
13905        });
13906        editor.begin_selection(
13907            Point::new(2, 1).to_display_point(&snapshot),
13908            true,
13909            1,
13910            window,
13911            cx,
13912        );
13913        assert_eq!(
13914            editor.selections.ranges(cx),
13915            [
13916                Point::new(1, 3)..Point::new(1, 3),
13917                Point::new(2, 1)..Point::new(2, 1),
13918            ]
13919        );
13920        editor
13921    });
13922
13923    // Refreshing selections is a no-op when excerpts haven't changed.
13924    _ = editor.update(cx, |editor, window, cx| {
13925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13926        assert_eq!(
13927            editor.selections.ranges(cx),
13928            [
13929                Point::new(1, 3)..Point::new(1, 3),
13930                Point::new(2, 1)..Point::new(2, 1),
13931            ]
13932        );
13933    });
13934
13935    multibuffer.update(cx, |multibuffer, cx| {
13936        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
13937    });
13938    _ = editor.update(cx, |editor, window, cx| {
13939        // Removing an excerpt causes the first selection to become degenerate.
13940        assert_eq!(
13941            editor.selections.ranges(cx),
13942            [
13943                Point::new(0, 0)..Point::new(0, 0),
13944                Point::new(0, 1)..Point::new(0, 1)
13945            ]
13946        );
13947
13948        // Refreshing selections will relocate the first selection to the original buffer
13949        // location.
13950        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
13951        assert_eq!(
13952            editor.selections.ranges(cx),
13953            [
13954                Point::new(0, 1)..Point::new(0, 1),
13955                Point::new(0, 3)..Point::new(0, 3)
13956            ]
13957        );
13958        assert!(editor.selections.pending_anchor().is_some());
13959    });
13960}
13961
13962#[gpui::test]
13963fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
13964    init_test(cx, |_| {});
13965
13966    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
13967    let mut excerpt1_id = None;
13968    let multibuffer = cx.new(|cx| {
13969        let mut multibuffer = MultiBuffer::new(ReadWrite);
13970        excerpt1_id = multibuffer
13971            .push_excerpts(
13972                buffer.clone(),
13973                [
13974                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
13975                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
13976                ],
13977                cx,
13978            )
13979            .into_iter()
13980            .next();
13981        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
13982        multibuffer
13983    });
13984
13985    let editor = cx.add_window(|window, cx| {
13986        let mut editor = build_editor(multibuffer.clone(), window, cx);
13987        let snapshot = editor.snapshot(window, cx);
13988        editor.begin_selection(
13989            Point::new(1, 3).to_display_point(&snapshot),
13990            false,
13991            1,
13992            window,
13993            cx,
13994        );
13995        assert_eq!(
13996            editor.selections.ranges(cx),
13997            [Point::new(1, 3)..Point::new(1, 3)]
13998        );
13999        editor
14000    });
14001
14002    multibuffer.update(cx, |multibuffer, cx| {
14003        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14004    });
14005    _ = editor.update(cx, |editor, window, cx| {
14006        assert_eq!(
14007            editor.selections.ranges(cx),
14008            [Point::new(0, 0)..Point::new(0, 0)]
14009        );
14010
14011        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14013        assert_eq!(
14014            editor.selections.ranges(cx),
14015            [Point::new(0, 3)..Point::new(0, 3)]
14016        );
14017        assert!(editor.selections.pending_anchor().is_some());
14018    });
14019}
14020
14021#[gpui::test]
14022async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14023    init_test(cx, |_| {});
14024
14025    let language = Arc::new(
14026        Language::new(
14027            LanguageConfig {
14028                brackets: BracketPairConfig {
14029                    pairs: vec![
14030                        BracketPair {
14031                            start: "{".to_string(),
14032                            end: "}".to_string(),
14033                            close: true,
14034                            surround: true,
14035                            newline: true,
14036                        },
14037                        BracketPair {
14038                            start: "/* ".to_string(),
14039                            end: " */".to_string(),
14040                            close: true,
14041                            surround: true,
14042                            newline: true,
14043                        },
14044                    ],
14045                    ..Default::default()
14046                },
14047                ..Default::default()
14048            },
14049            Some(tree_sitter_rust::LANGUAGE.into()),
14050        )
14051        .with_indents_query("")
14052        .unwrap(),
14053    );
14054
14055    let text = concat!(
14056        "{   }\n",     //
14057        "  x\n",       //
14058        "  /*   */\n", //
14059        "x\n",         //
14060        "{{} }\n",     //
14061    );
14062
14063    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14064    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14065    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14066    editor
14067        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14068        .await;
14069
14070    editor.update_in(cx, |editor, window, cx| {
14071        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14072            s.select_display_ranges([
14073                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14074                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14075                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14076            ])
14077        });
14078        editor.newline(&Newline, window, cx);
14079
14080        assert_eq!(
14081            editor.buffer().read(cx).read(cx).text(),
14082            concat!(
14083                "{ \n",    // Suppress rustfmt
14084                "\n",      //
14085                "}\n",     //
14086                "  x\n",   //
14087                "  /* \n", //
14088                "  \n",    //
14089                "  */\n",  //
14090                "x\n",     //
14091                "{{} \n",  //
14092                "}\n",     //
14093            )
14094        );
14095    });
14096}
14097
14098#[gpui::test]
14099fn test_highlighted_ranges(cx: &mut TestAppContext) {
14100    init_test(cx, |_| {});
14101
14102    let editor = cx.add_window(|window, cx| {
14103        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14104        build_editor(buffer.clone(), window, cx)
14105    });
14106
14107    _ = editor.update(cx, |editor, window, cx| {
14108        struct Type1;
14109        struct Type2;
14110
14111        let buffer = editor.buffer.read(cx).snapshot(cx);
14112
14113        let anchor_range =
14114            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14115
14116        editor.highlight_background::<Type1>(
14117            &[
14118                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14119                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14120                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14121                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14122            ],
14123            |_| Hsla::red(),
14124            cx,
14125        );
14126        editor.highlight_background::<Type2>(
14127            &[
14128                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14129                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14130                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14131                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14132            ],
14133            |_| Hsla::green(),
14134            cx,
14135        );
14136
14137        let snapshot = editor.snapshot(window, cx);
14138        let mut highlighted_ranges = editor.background_highlights_in_range(
14139            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14140            &snapshot,
14141            cx.theme(),
14142        );
14143        // Enforce a consistent ordering based on color without relying on the ordering of the
14144        // highlight's `TypeId` which is non-executor.
14145        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14146        assert_eq!(
14147            highlighted_ranges,
14148            &[
14149                (
14150                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14151                    Hsla::red(),
14152                ),
14153                (
14154                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14155                    Hsla::red(),
14156                ),
14157                (
14158                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14159                    Hsla::green(),
14160                ),
14161                (
14162                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14163                    Hsla::green(),
14164                ),
14165            ]
14166        );
14167        assert_eq!(
14168            editor.background_highlights_in_range(
14169                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14170                &snapshot,
14171                cx.theme(),
14172            ),
14173            &[(
14174                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14175                Hsla::red(),
14176            )]
14177        );
14178    });
14179}
14180
14181#[gpui::test]
14182async fn test_following(cx: &mut TestAppContext) {
14183    init_test(cx, |_| {});
14184
14185    let fs = FakeFs::new(cx.executor());
14186    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14187
14188    let buffer = project.update(cx, |project, cx| {
14189        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14190        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14191    });
14192    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14193    let follower = cx.update(|cx| {
14194        cx.open_window(
14195            WindowOptions {
14196                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14197                    gpui::Point::new(px(0.), px(0.)),
14198                    gpui::Point::new(px(10.), px(80.)),
14199                ))),
14200                ..Default::default()
14201            },
14202            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14203        )
14204        .unwrap()
14205    });
14206
14207    let is_still_following = Rc::new(RefCell::new(true));
14208    let follower_edit_event_count = Rc::new(RefCell::new(0));
14209    let pending_update = Rc::new(RefCell::new(None));
14210    let leader_entity = leader.root(cx).unwrap();
14211    let follower_entity = follower.root(cx).unwrap();
14212    _ = follower.update(cx, {
14213        let update = pending_update.clone();
14214        let is_still_following = is_still_following.clone();
14215        let follower_edit_event_count = follower_edit_event_count.clone();
14216        |_, window, cx| {
14217            cx.subscribe_in(
14218                &leader_entity,
14219                window,
14220                move |_, leader, event, window, cx| {
14221                    leader.read(cx).add_event_to_update_proto(
14222                        event,
14223                        &mut update.borrow_mut(),
14224                        window,
14225                        cx,
14226                    );
14227                },
14228            )
14229            .detach();
14230
14231            cx.subscribe_in(
14232                &follower_entity,
14233                window,
14234                move |_, _, event: &EditorEvent, _window, _cx| {
14235                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
14236                        *is_still_following.borrow_mut() = false;
14237                    }
14238
14239                    if let EditorEvent::BufferEdited = event {
14240                        *follower_edit_event_count.borrow_mut() += 1;
14241                    }
14242                },
14243            )
14244            .detach();
14245        }
14246    });
14247
14248    // Update the selections only
14249    _ = leader.update(cx, |leader, window, cx| {
14250        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14251            s.select_ranges([1..1])
14252        });
14253    });
14254    follower
14255        .update(cx, |follower, window, cx| {
14256            follower.apply_update_proto(
14257                &project,
14258                pending_update.borrow_mut().take().unwrap(),
14259                window,
14260                cx,
14261            )
14262        })
14263        .unwrap()
14264        .await
14265        .unwrap();
14266    _ = follower.update(cx, |follower, _, cx| {
14267        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
14268    });
14269    assert!(*is_still_following.borrow());
14270    assert_eq!(*follower_edit_event_count.borrow(), 0);
14271
14272    // Update the scroll position only
14273    _ = leader.update(cx, |leader, window, cx| {
14274        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14275    });
14276    follower
14277        .update(cx, |follower, window, cx| {
14278            follower.apply_update_proto(
14279                &project,
14280                pending_update.borrow_mut().take().unwrap(),
14281                window,
14282                cx,
14283            )
14284        })
14285        .unwrap()
14286        .await
14287        .unwrap();
14288    assert_eq!(
14289        follower
14290            .update(cx, |follower, _, cx| follower.scroll_position(cx))
14291            .unwrap(),
14292        gpui::Point::new(1.5, 3.5)
14293    );
14294    assert!(*is_still_following.borrow());
14295    assert_eq!(*follower_edit_event_count.borrow(), 0);
14296
14297    // Update the selections and scroll position. The follower's scroll position is updated
14298    // via autoscroll, not via the leader's exact scroll position.
14299    _ = leader.update(cx, |leader, window, cx| {
14300        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14301            s.select_ranges([0..0])
14302        });
14303        leader.request_autoscroll(Autoscroll::newest(), cx);
14304        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
14305    });
14306    follower
14307        .update(cx, |follower, window, cx| {
14308            follower.apply_update_proto(
14309                &project,
14310                pending_update.borrow_mut().take().unwrap(),
14311                window,
14312                cx,
14313            )
14314        })
14315        .unwrap()
14316        .await
14317        .unwrap();
14318    _ = follower.update(cx, |follower, _, cx| {
14319        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
14320        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
14321    });
14322    assert!(*is_still_following.borrow());
14323
14324    // Creating a pending selection that precedes another selection
14325    _ = leader.update(cx, |leader, window, cx| {
14326        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14327            s.select_ranges([1..1])
14328        });
14329        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
14330    });
14331    follower
14332        .update(cx, |follower, window, cx| {
14333            follower.apply_update_proto(
14334                &project,
14335                pending_update.borrow_mut().take().unwrap(),
14336                window,
14337                cx,
14338            )
14339        })
14340        .unwrap()
14341        .await
14342        .unwrap();
14343    _ = follower.update(cx, |follower, _, cx| {
14344        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
14345    });
14346    assert!(*is_still_following.borrow());
14347
14348    // Extend the pending selection so that it surrounds another selection
14349    _ = leader.update(cx, |leader, window, cx| {
14350        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
14351    });
14352    follower
14353        .update(cx, |follower, window, cx| {
14354            follower.apply_update_proto(
14355                &project,
14356                pending_update.borrow_mut().take().unwrap(),
14357                window,
14358                cx,
14359            )
14360        })
14361        .unwrap()
14362        .await
14363        .unwrap();
14364    _ = follower.update(cx, |follower, _, cx| {
14365        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
14366    });
14367
14368    // Scrolling locally breaks the follow
14369    _ = follower.update(cx, |follower, window, cx| {
14370        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
14371        follower.set_scroll_anchor(
14372            ScrollAnchor {
14373                anchor: top_anchor,
14374                offset: gpui::Point::new(0.0, 0.5),
14375            },
14376            window,
14377            cx,
14378        );
14379    });
14380    assert!(!(*is_still_following.borrow()));
14381}
14382
14383#[gpui::test]
14384async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
14385    init_test(cx, |_| {});
14386
14387    let fs = FakeFs::new(cx.executor());
14388    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14389    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14390    let pane = workspace
14391        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14392        .unwrap();
14393
14394    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14395
14396    let leader = pane.update_in(cx, |_, window, cx| {
14397        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
14398        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
14399    });
14400
14401    // Start following the editor when it has no excerpts.
14402    let mut state_message =
14403        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14404    let workspace_entity = workspace.root(cx).unwrap();
14405    let follower_1 = cx
14406        .update_window(*workspace.deref(), |_, window, cx| {
14407            Editor::from_state_proto(
14408                workspace_entity,
14409                ViewId {
14410                    creator: CollaboratorId::PeerId(PeerId::default()),
14411                    id: 0,
14412                },
14413                &mut state_message,
14414                window,
14415                cx,
14416            )
14417        })
14418        .unwrap()
14419        .unwrap()
14420        .await
14421        .unwrap();
14422
14423    let update_message = Rc::new(RefCell::new(None));
14424    follower_1.update_in(cx, {
14425        let update = update_message.clone();
14426        |_, window, cx| {
14427            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
14428                leader.read(cx).add_event_to_update_proto(
14429                    event,
14430                    &mut update.borrow_mut(),
14431                    window,
14432                    cx,
14433                );
14434            })
14435            .detach();
14436        }
14437    });
14438
14439    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
14440        (
14441            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
14442            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
14443        )
14444    });
14445
14446    // Insert some excerpts.
14447    leader.update(cx, |leader, cx| {
14448        leader.buffer.update(cx, |multibuffer, cx| {
14449            multibuffer.set_excerpts_for_path(
14450                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
14451                buffer_1.clone(),
14452                vec![
14453                    Point::row_range(0..3),
14454                    Point::row_range(1..6),
14455                    Point::row_range(12..15),
14456                ],
14457                0,
14458                cx,
14459            );
14460            multibuffer.set_excerpts_for_path(
14461                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
14462                buffer_2.clone(),
14463                vec![Point::row_range(0..6), Point::row_range(8..12)],
14464                0,
14465                cx,
14466            );
14467        });
14468    });
14469
14470    // Apply the update of adding the excerpts.
14471    follower_1
14472        .update_in(cx, |follower, window, cx| {
14473            follower.apply_update_proto(
14474                &project,
14475                update_message.borrow().clone().unwrap(),
14476                window,
14477                cx,
14478            )
14479        })
14480        .await
14481        .unwrap();
14482    assert_eq!(
14483        follower_1.update(cx, |editor, cx| editor.text(cx)),
14484        leader.update(cx, |editor, cx| editor.text(cx))
14485    );
14486    update_message.borrow_mut().take();
14487
14488    // Start following separately after it already has excerpts.
14489    let mut state_message =
14490        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
14491    let workspace_entity = workspace.root(cx).unwrap();
14492    let follower_2 = cx
14493        .update_window(*workspace.deref(), |_, window, cx| {
14494            Editor::from_state_proto(
14495                workspace_entity,
14496                ViewId {
14497                    creator: CollaboratorId::PeerId(PeerId::default()),
14498                    id: 0,
14499                },
14500                &mut state_message,
14501                window,
14502                cx,
14503            )
14504        })
14505        .unwrap()
14506        .unwrap()
14507        .await
14508        .unwrap();
14509    assert_eq!(
14510        follower_2.update(cx, |editor, cx| editor.text(cx)),
14511        leader.update(cx, |editor, cx| editor.text(cx))
14512    );
14513
14514    // Remove some excerpts.
14515    leader.update(cx, |leader, cx| {
14516        leader.buffer.update(cx, |multibuffer, cx| {
14517            let excerpt_ids = multibuffer.excerpt_ids();
14518            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
14519            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
14520        });
14521    });
14522
14523    // Apply the update of removing the excerpts.
14524    follower_1
14525        .update_in(cx, |follower, window, cx| {
14526            follower.apply_update_proto(
14527                &project,
14528                update_message.borrow().clone().unwrap(),
14529                window,
14530                cx,
14531            )
14532        })
14533        .await
14534        .unwrap();
14535    follower_2
14536        .update_in(cx, |follower, window, cx| {
14537            follower.apply_update_proto(
14538                &project,
14539                update_message.borrow().clone().unwrap(),
14540                window,
14541                cx,
14542            )
14543        })
14544        .await
14545        .unwrap();
14546    update_message.borrow_mut().take();
14547    assert_eq!(
14548        follower_1.update(cx, |editor, cx| editor.text(cx)),
14549        leader.update(cx, |editor, cx| editor.text(cx))
14550    );
14551}
14552
14553#[gpui::test]
14554async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14555    init_test(cx, |_| {});
14556
14557    let mut cx = EditorTestContext::new(cx).await;
14558    let lsp_store =
14559        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
14560
14561    cx.set_state(indoc! {"
14562        ˇfn func(abc def: i32) -> u32 {
14563        }
14564    "});
14565
14566    cx.update(|_, cx| {
14567        lsp_store.update(cx, |lsp_store, cx| {
14568            lsp_store
14569                .update_diagnostics(
14570                    LanguageServerId(0),
14571                    lsp::PublishDiagnosticsParams {
14572                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
14573                        version: None,
14574                        diagnostics: vec![
14575                            lsp::Diagnostic {
14576                                range: lsp::Range::new(
14577                                    lsp::Position::new(0, 11),
14578                                    lsp::Position::new(0, 12),
14579                                ),
14580                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14581                                ..Default::default()
14582                            },
14583                            lsp::Diagnostic {
14584                                range: lsp::Range::new(
14585                                    lsp::Position::new(0, 12),
14586                                    lsp::Position::new(0, 15),
14587                                ),
14588                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14589                                ..Default::default()
14590                            },
14591                            lsp::Diagnostic {
14592                                range: lsp::Range::new(
14593                                    lsp::Position::new(0, 25),
14594                                    lsp::Position::new(0, 28),
14595                                ),
14596                                severity: Some(lsp::DiagnosticSeverity::ERROR),
14597                                ..Default::default()
14598                            },
14599                        ],
14600                    },
14601                    None,
14602                    DiagnosticSourceKind::Pushed,
14603                    &[],
14604                    cx,
14605                )
14606                .unwrap()
14607        });
14608    });
14609
14610    executor.run_until_parked();
14611
14612    cx.update_editor(|editor, window, cx| {
14613        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14614    });
14615
14616    cx.assert_editor_state(indoc! {"
14617        fn func(abc def: i32) -> ˇu32 {
14618        }
14619    "});
14620
14621    cx.update_editor(|editor, window, cx| {
14622        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14623    });
14624
14625    cx.assert_editor_state(indoc! {"
14626        fn func(abc ˇdef: i32) -> u32 {
14627        }
14628    "});
14629
14630    cx.update_editor(|editor, window, cx| {
14631        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14632    });
14633
14634    cx.assert_editor_state(indoc! {"
14635        fn func(abcˇ def: i32) -> u32 {
14636        }
14637    "});
14638
14639    cx.update_editor(|editor, window, cx| {
14640        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
14641    });
14642
14643    cx.assert_editor_state(indoc! {"
14644        fn func(abc def: i32) -> ˇu32 {
14645        }
14646    "});
14647}
14648
14649#[gpui::test]
14650async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14651    init_test(cx, |_| {});
14652
14653    let mut cx = EditorTestContext::new(cx).await;
14654
14655    let diff_base = r#"
14656        use some::mod;
14657
14658        const A: u32 = 42;
14659
14660        fn main() {
14661            println!("hello");
14662
14663            println!("world");
14664        }
14665        "#
14666    .unindent();
14667
14668    // Edits are modified, removed, modified, added
14669    cx.set_state(
14670        &r#"
14671        use some::modified;
14672
14673        ˇ
14674        fn main() {
14675            println!("hello there");
14676
14677            println!("around the");
14678            println!("world");
14679        }
14680        "#
14681        .unindent(),
14682    );
14683
14684    cx.set_head_text(&diff_base);
14685    executor.run_until_parked();
14686
14687    cx.update_editor(|editor, window, cx| {
14688        //Wrap around the bottom of the buffer
14689        for _ in 0..3 {
14690            editor.go_to_next_hunk(&GoToHunk, window, cx);
14691        }
14692    });
14693
14694    cx.assert_editor_state(
14695        &r#"
14696        ˇuse some::modified;
14697
14698
14699        fn main() {
14700            println!("hello there");
14701
14702            println!("around the");
14703            println!("world");
14704        }
14705        "#
14706        .unindent(),
14707    );
14708
14709    cx.update_editor(|editor, window, cx| {
14710        //Wrap around the top of the buffer
14711        for _ in 0..2 {
14712            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14713        }
14714    });
14715
14716    cx.assert_editor_state(
14717        &r#"
14718        use some::modified;
14719
14720
14721        fn main() {
14722        ˇ    println!("hello there");
14723
14724            println!("around the");
14725            println!("world");
14726        }
14727        "#
14728        .unindent(),
14729    );
14730
14731    cx.update_editor(|editor, window, cx| {
14732        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14733    });
14734
14735    cx.assert_editor_state(
14736        &r#"
14737        use some::modified;
14738
14739        ˇ
14740        fn main() {
14741            println!("hello there");
14742
14743            println!("around the");
14744            println!("world");
14745        }
14746        "#
14747        .unindent(),
14748    );
14749
14750    cx.update_editor(|editor, window, cx| {
14751        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14752    });
14753
14754    cx.assert_editor_state(
14755        &r#"
14756        ˇuse some::modified;
14757
14758
14759        fn main() {
14760            println!("hello there");
14761
14762            println!("around the");
14763            println!("world");
14764        }
14765        "#
14766        .unindent(),
14767    );
14768
14769    cx.update_editor(|editor, window, cx| {
14770        for _ in 0..2 {
14771            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
14772        }
14773    });
14774
14775    cx.assert_editor_state(
14776        &r#"
14777        use some::modified;
14778
14779
14780        fn main() {
14781        ˇ    println!("hello there");
14782
14783            println!("around the");
14784            println!("world");
14785        }
14786        "#
14787        .unindent(),
14788    );
14789
14790    cx.update_editor(|editor, window, cx| {
14791        editor.fold(&Fold, window, cx);
14792    });
14793
14794    cx.update_editor(|editor, window, cx| {
14795        editor.go_to_next_hunk(&GoToHunk, window, cx);
14796    });
14797
14798    cx.assert_editor_state(
14799        &r#"
14800        ˇuse some::modified;
14801
14802
14803        fn main() {
14804            println!("hello there");
14805
14806            println!("around the");
14807            println!("world");
14808        }
14809        "#
14810        .unindent(),
14811    );
14812}
14813
14814#[test]
14815fn test_split_words() {
14816    fn split(text: &str) -> Vec<&str> {
14817        split_words(text).collect()
14818    }
14819
14820    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
14821    assert_eq!(split("hello_world"), &["hello_", "world"]);
14822    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
14823    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
14824    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
14825    assert_eq!(split("helloworld"), &["helloworld"]);
14826
14827    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
14828}
14829
14830#[gpui::test]
14831async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
14832    init_test(cx, |_| {});
14833
14834    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
14835    let mut assert = |before, after| {
14836        let _state_context = cx.set_state(before);
14837        cx.run_until_parked();
14838        cx.update_editor(|editor, window, cx| {
14839            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
14840        });
14841        cx.run_until_parked();
14842        cx.assert_editor_state(after);
14843    };
14844
14845    // Outside bracket jumps to outside of matching bracket
14846    assert("console.logˇ(var);", "console.log(var)ˇ;");
14847    assert("console.log(var)ˇ;", "console.logˇ(var);");
14848
14849    // Inside bracket jumps to inside of matching bracket
14850    assert("console.log(ˇvar);", "console.log(varˇ);");
14851    assert("console.log(varˇ);", "console.log(ˇvar);");
14852
14853    // When outside a bracket and inside, favor jumping to the inside bracket
14854    assert(
14855        "console.log('foo', [1, 2, 3]ˇ);",
14856        "console.log(ˇ'foo', [1, 2, 3]);",
14857    );
14858    assert(
14859        "console.log(ˇ'foo', [1, 2, 3]);",
14860        "console.log('foo', [1, 2, 3]ˇ);",
14861    );
14862
14863    // Bias forward if two options are equally likely
14864    assert(
14865        "let result = curried_fun()ˇ();",
14866        "let result = curried_fun()()ˇ;",
14867    );
14868
14869    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
14870    assert(
14871        indoc! {"
14872            function test() {
14873                console.log('test')ˇ
14874            }"},
14875        indoc! {"
14876            function test() {
14877                console.logˇ('test')
14878            }"},
14879    );
14880}
14881
14882#[gpui::test]
14883async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
14884    init_test(cx, |_| {});
14885
14886    let fs = FakeFs::new(cx.executor());
14887    fs.insert_tree(
14888        path!("/a"),
14889        json!({
14890            "main.rs": "fn main() { let a = 5; }",
14891            "other.rs": "// Test file",
14892        }),
14893    )
14894    .await;
14895    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14896
14897    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14898    language_registry.add(Arc::new(Language::new(
14899        LanguageConfig {
14900            name: "Rust".into(),
14901            matcher: LanguageMatcher {
14902                path_suffixes: vec!["rs".to_string()],
14903                ..Default::default()
14904            },
14905            brackets: BracketPairConfig {
14906                pairs: vec![BracketPair {
14907                    start: "{".to_string(),
14908                    end: "}".to_string(),
14909                    close: true,
14910                    surround: true,
14911                    newline: true,
14912                }],
14913                disabled_scopes_by_bracket_ix: Vec::new(),
14914            },
14915            ..Default::default()
14916        },
14917        Some(tree_sitter_rust::LANGUAGE.into()),
14918    )));
14919    let mut fake_servers = language_registry.register_fake_lsp(
14920        "Rust",
14921        FakeLspAdapter {
14922            capabilities: lsp::ServerCapabilities {
14923                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
14924                    first_trigger_character: "{".to_string(),
14925                    more_trigger_character: None,
14926                }),
14927                ..Default::default()
14928            },
14929            ..Default::default()
14930        },
14931    );
14932
14933    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14934
14935    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14936
14937    let worktree_id = workspace
14938        .update(cx, |workspace, _, cx| {
14939            workspace.project().update(cx, |project, cx| {
14940                project.worktrees(cx).next().unwrap().read(cx).id()
14941            })
14942        })
14943        .unwrap();
14944
14945    let buffer = project
14946        .update(cx, |project, cx| {
14947            project.open_local_buffer(path!("/a/main.rs"), cx)
14948        })
14949        .await
14950        .unwrap();
14951    let editor_handle = workspace
14952        .update(cx, |workspace, window, cx| {
14953            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
14954        })
14955        .unwrap()
14956        .await
14957        .unwrap()
14958        .downcast::<Editor>()
14959        .unwrap();
14960
14961    cx.executor().start_waiting();
14962    let fake_server = fake_servers.next().await.unwrap();
14963
14964    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
14965        |params, _| async move {
14966            assert_eq!(
14967                params.text_document_position.text_document.uri,
14968                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
14969            );
14970            assert_eq!(
14971                params.text_document_position.position,
14972                lsp::Position::new(0, 21),
14973            );
14974
14975            Ok(Some(vec![lsp::TextEdit {
14976                new_text: "]".to_string(),
14977                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
14978            }]))
14979        },
14980    );
14981
14982    editor_handle.update_in(cx, |editor, window, cx| {
14983        window.focus(&editor.focus_handle(cx));
14984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14985            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
14986        });
14987        editor.handle_input("{", window, cx);
14988    });
14989
14990    cx.executor().run_until_parked();
14991
14992    buffer.update(cx, |buffer, _| {
14993        assert_eq!(
14994            buffer.text(),
14995            "fn main() { let a = {5}; }",
14996            "No extra braces from on type formatting should appear in the buffer"
14997        )
14998    });
14999}
15000
15001#[gpui::test(iterations = 20, seeds(31))]
15002async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15003    init_test(cx, |_| {});
15004
15005    let mut cx = EditorLspTestContext::new_rust(
15006        lsp::ServerCapabilities {
15007            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15008                first_trigger_character: ".".to_string(),
15009                more_trigger_character: None,
15010            }),
15011            ..Default::default()
15012        },
15013        cx,
15014    )
15015    .await;
15016
15017    cx.update_buffer(|buffer, _| {
15018        // This causes autoindent to be async.
15019        buffer.set_sync_parse_timeout(Duration::ZERO)
15020    });
15021
15022    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15023    cx.simulate_keystroke("\n");
15024    cx.run_until_parked();
15025
15026    let buffer_cloned =
15027        cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap().clone());
15028    let mut request =
15029        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15030            let buffer_cloned = buffer_cloned.clone();
15031            async move {
15032                buffer_cloned.update(&mut cx, |buffer, _| {
15033                    assert_eq!(
15034                        buffer.text(),
15035                        "fn c() {\n    d()\n        .\n}\n",
15036                        "OnTypeFormatting should triggered after autoindent applied"
15037                    )
15038                })?;
15039
15040                Ok(Some(vec![]))
15041            }
15042        });
15043
15044    cx.simulate_keystroke(".");
15045    cx.run_until_parked();
15046
15047    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15048    assert!(request.next().await.is_some());
15049    request.close();
15050    assert!(request.next().await.is_none());
15051}
15052
15053#[gpui::test]
15054async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15055    init_test(cx, |_| {});
15056
15057    let fs = FakeFs::new(cx.executor());
15058    fs.insert_tree(
15059        path!("/a"),
15060        json!({
15061            "main.rs": "fn main() { let a = 5; }",
15062            "other.rs": "// Test file",
15063        }),
15064    )
15065    .await;
15066
15067    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15068
15069    let server_restarts = Arc::new(AtomicUsize::new(0));
15070    let closure_restarts = Arc::clone(&server_restarts);
15071    let language_server_name = "test language server";
15072    let language_name: LanguageName = "Rust".into();
15073
15074    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15075    language_registry.add(Arc::new(Language::new(
15076        LanguageConfig {
15077            name: language_name.clone(),
15078            matcher: LanguageMatcher {
15079                path_suffixes: vec!["rs".to_string()],
15080                ..Default::default()
15081            },
15082            ..Default::default()
15083        },
15084        Some(tree_sitter_rust::LANGUAGE.into()),
15085    )));
15086    let mut fake_servers = language_registry.register_fake_lsp(
15087        "Rust",
15088        FakeLspAdapter {
15089            name: language_server_name,
15090            initialization_options: Some(json!({
15091                "testOptionValue": true
15092            })),
15093            initializer: Some(Box::new(move |fake_server| {
15094                let task_restarts = Arc::clone(&closure_restarts);
15095                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15096                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15097                    futures::future::ready(Ok(()))
15098                });
15099            })),
15100            ..Default::default()
15101        },
15102    );
15103
15104    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15105    let _buffer = project
15106        .update(cx, |project, cx| {
15107            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15108        })
15109        .await
15110        .unwrap();
15111    let _fake_server = fake_servers.next().await.unwrap();
15112    update_test_language_settings(cx, |language_settings| {
15113        language_settings.languages.0.insert(
15114            language_name.clone(),
15115            LanguageSettingsContent {
15116                tab_size: NonZeroU32::new(8),
15117                ..Default::default()
15118            },
15119        );
15120    });
15121    cx.executor().run_until_parked();
15122    assert_eq!(
15123        server_restarts.load(atomic::Ordering::Acquire),
15124        0,
15125        "Should not restart LSP server on an unrelated change"
15126    );
15127
15128    update_test_project_settings(cx, |project_settings| {
15129        project_settings.lsp.insert(
15130            "Some other server name".into(),
15131            LspSettings {
15132                binary: None,
15133                settings: None,
15134                initialization_options: Some(json!({
15135                    "some other init value": false
15136                })),
15137                enable_lsp_tasks: false,
15138            },
15139        );
15140    });
15141    cx.executor().run_until_parked();
15142    assert_eq!(
15143        server_restarts.load(atomic::Ordering::Acquire),
15144        0,
15145        "Should not restart LSP server on an unrelated LSP settings change"
15146    );
15147
15148    update_test_project_settings(cx, |project_settings| {
15149        project_settings.lsp.insert(
15150            language_server_name.into(),
15151            LspSettings {
15152                binary: None,
15153                settings: None,
15154                initialization_options: Some(json!({
15155                    "anotherInitValue": false
15156                })),
15157                enable_lsp_tasks: false,
15158            },
15159        );
15160    });
15161    cx.executor().run_until_parked();
15162    assert_eq!(
15163        server_restarts.load(atomic::Ordering::Acquire),
15164        1,
15165        "Should restart LSP server on a related LSP settings change"
15166    );
15167
15168    update_test_project_settings(cx, |project_settings| {
15169        project_settings.lsp.insert(
15170            language_server_name.into(),
15171            LspSettings {
15172                binary: None,
15173                settings: None,
15174                initialization_options: Some(json!({
15175                    "anotherInitValue": false
15176                })),
15177                enable_lsp_tasks: false,
15178            },
15179        );
15180    });
15181    cx.executor().run_until_parked();
15182    assert_eq!(
15183        server_restarts.load(atomic::Ordering::Acquire),
15184        1,
15185        "Should not restart LSP server on a related LSP settings change that is the same"
15186    );
15187
15188    update_test_project_settings(cx, |project_settings| {
15189        project_settings.lsp.insert(
15190            language_server_name.into(),
15191            LspSettings {
15192                binary: None,
15193                settings: None,
15194                initialization_options: None,
15195                enable_lsp_tasks: false,
15196            },
15197        );
15198    });
15199    cx.executor().run_until_parked();
15200    assert_eq!(
15201        server_restarts.load(atomic::Ordering::Acquire),
15202        2,
15203        "Should restart LSP server on another related LSP settings change"
15204    );
15205}
15206
15207#[gpui::test]
15208async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15209    init_test(cx, |_| {});
15210
15211    let mut cx = EditorLspTestContext::new_rust(
15212        lsp::ServerCapabilities {
15213            completion_provider: Some(lsp::CompletionOptions {
15214                trigger_characters: Some(vec![".".to_string()]),
15215                resolve_provider: Some(true),
15216                ..Default::default()
15217            }),
15218            ..Default::default()
15219        },
15220        cx,
15221    )
15222    .await;
15223
15224    cx.set_state("fn main() { let a = 2ˇ; }");
15225    cx.simulate_keystroke(".");
15226    let completion_item = lsp::CompletionItem {
15227        label: "some".into(),
15228        kind: Some(lsp::CompletionItemKind::SNIPPET),
15229        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15230        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15231            kind: lsp::MarkupKind::Markdown,
15232            value: "```rust\nSome(2)\n```".to_string(),
15233        })),
15234        deprecated: Some(false),
15235        sort_text: Some("fffffff2".to_string()),
15236        filter_text: Some("some".to_string()),
15237        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15238        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15239            range: lsp::Range {
15240                start: lsp::Position {
15241                    line: 0,
15242                    character: 22,
15243                },
15244                end: lsp::Position {
15245                    line: 0,
15246                    character: 22,
15247                },
15248            },
15249            new_text: "Some(2)".to_string(),
15250        })),
15251        additional_text_edits: Some(vec![lsp::TextEdit {
15252            range: lsp::Range {
15253                start: lsp::Position {
15254                    line: 0,
15255                    character: 20,
15256                },
15257                end: lsp::Position {
15258                    line: 0,
15259                    character: 22,
15260                },
15261            },
15262            new_text: "".to_string(),
15263        }]),
15264        ..Default::default()
15265    };
15266
15267    let closure_completion_item = completion_item.clone();
15268    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15269        let task_completion_item = closure_completion_item.clone();
15270        async move {
15271            Ok(Some(lsp::CompletionResponse::Array(vec![
15272                task_completion_item,
15273            ])))
15274        }
15275    });
15276
15277    request.next().await;
15278
15279    cx.condition(|editor, _| editor.context_menu_visible())
15280        .await;
15281    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15282        editor
15283            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15284            .unwrap()
15285    });
15286    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
15287
15288    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15289        let task_completion_item = completion_item.clone();
15290        async move { Ok(task_completion_item) }
15291    })
15292    .next()
15293    .await
15294    .unwrap();
15295    apply_additional_edits.await.unwrap();
15296    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
15297}
15298
15299#[gpui::test]
15300async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
15301    init_test(cx, |_| {});
15302
15303    let mut cx = EditorLspTestContext::new_rust(
15304        lsp::ServerCapabilities {
15305            completion_provider: Some(lsp::CompletionOptions {
15306                trigger_characters: Some(vec![".".to_string()]),
15307                resolve_provider: Some(true),
15308                ..Default::default()
15309            }),
15310            ..Default::default()
15311        },
15312        cx,
15313    )
15314    .await;
15315
15316    cx.set_state("fn main() { let a = 2ˇ; }");
15317    cx.simulate_keystroke(".");
15318
15319    let item1 = lsp::CompletionItem {
15320        label: "method id()".to_string(),
15321        filter_text: Some("id".to_string()),
15322        detail: None,
15323        documentation: None,
15324        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15325            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15326            new_text: ".id".to_string(),
15327        })),
15328        ..lsp::CompletionItem::default()
15329    };
15330
15331    let item2 = lsp::CompletionItem {
15332        label: "other".to_string(),
15333        filter_text: Some("other".to_string()),
15334        detail: None,
15335        documentation: None,
15336        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15337            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15338            new_text: ".other".to_string(),
15339        })),
15340        ..lsp::CompletionItem::default()
15341    };
15342
15343    let item1 = item1.clone();
15344    cx.set_request_handler::<lsp::request::Completion, _, _>({
15345        let item1 = item1.clone();
15346        move |_, _, _| {
15347            let item1 = item1.clone();
15348            let item2 = item2.clone();
15349            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
15350        }
15351    })
15352    .next()
15353    .await;
15354
15355    cx.condition(|editor, _| editor.context_menu_visible())
15356        .await;
15357    cx.update_editor(|editor, _, _| {
15358        let context_menu = editor.context_menu.borrow_mut();
15359        let context_menu = context_menu
15360            .as_ref()
15361            .expect("Should have the context menu deployed");
15362        match context_menu {
15363            CodeContextMenu::Completions(completions_menu) => {
15364                let completions = completions_menu.completions.borrow_mut();
15365                assert_eq!(
15366                    completions
15367                        .iter()
15368                        .map(|completion| &completion.label.text)
15369                        .collect::<Vec<_>>(),
15370                    vec!["method id()", "other"]
15371                )
15372            }
15373            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15374        }
15375    });
15376
15377    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
15378        let item1 = item1.clone();
15379        move |_, item_to_resolve, _| {
15380            let item1 = item1.clone();
15381            async move {
15382                if item1 == item_to_resolve {
15383                    Ok(lsp::CompletionItem {
15384                        label: "method id()".to_string(),
15385                        filter_text: Some("id".to_string()),
15386                        detail: Some("Now resolved!".to_string()),
15387                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
15388                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15389                            range: lsp::Range::new(
15390                                lsp::Position::new(0, 22),
15391                                lsp::Position::new(0, 22),
15392                            ),
15393                            new_text: ".id".to_string(),
15394                        })),
15395                        ..lsp::CompletionItem::default()
15396                    })
15397                } else {
15398                    Ok(item_to_resolve)
15399                }
15400            }
15401        }
15402    })
15403    .next()
15404    .await
15405    .unwrap();
15406    cx.run_until_parked();
15407
15408    cx.update_editor(|editor, window, cx| {
15409        editor.context_menu_next(&Default::default(), window, cx);
15410    });
15411
15412    cx.update_editor(|editor, _, _| {
15413        let context_menu = editor.context_menu.borrow_mut();
15414        let context_menu = context_menu
15415            .as_ref()
15416            .expect("Should have the context menu deployed");
15417        match context_menu {
15418            CodeContextMenu::Completions(completions_menu) => {
15419                let completions = completions_menu.completions.borrow_mut();
15420                assert_eq!(
15421                    completions
15422                        .iter()
15423                        .map(|completion| &completion.label.text)
15424                        .collect::<Vec<_>>(),
15425                    vec!["method id() Now resolved!", "other"],
15426                    "Should update first completion label, but not second as the filter text did not match."
15427                );
15428            }
15429            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15430        }
15431    });
15432}
15433
15434#[gpui::test]
15435async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
15436    init_test(cx, |_| {});
15437    let mut cx = EditorLspTestContext::new_rust(
15438        lsp::ServerCapabilities {
15439            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
15440            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
15441            completion_provider: Some(lsp::CompletionOptions {
15442                resolve_provider: Some(true),
15443                ..Default::default()
15444            }),
15445            ..Default::default()
15446        },
15447        cx,
15448    )
15449    .await;
15450    cx.set_state(indoc! {"
15451        struct TestStruct {
15452            field: i32
15453        }
15454
15455        fn mainˇ() {
15456            let unused_var = 42;
15457            let test_struct = TestStruct { field: 42 };
15458        }
15459    "});
15460    let symbol_range = cx.lsp_range(indoc! {"
15461        struct TestStruct {
15462            field: i32
15463        }
15464
15465        «fn main»() {
15466            let unused_var = 42;
15467            let test_struct = TestStruct { field: 42 };
15468        }
15469    "});
15470    let mut hover_requests =
15471        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
15472            Ok(Some(lsp::Hover {
15473                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
15474                    kind: lsp::MarkupKind::Markdown,
15475                    value: "Function documentation".to_string(),
15476                }),
15477                range: Some(symbol_range),
15478            }))
15479        });
15480
15481    // Case 1: Test that code action menu hide hover popover
15482    cx.dispatch_action(Hover);
15483    hover_requests.next().await;
15484    cx.condition(|editor, _| editor.hover_state.visible()).await;
15485    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
15486        move |_, _, _| async move {
15487            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
15488                lsp::CodeAction {
15489                    title: "Remove unused variable".to_string(),
15490                    kind: Some(CodeActionKind::QUICKFIX),
15491                    edit: Some(lsp::WorkspaceEdit {
15492                        changes: Some(
15493                            [(
15494                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
15495                                vec![lsp::TextEdit {
15496                                    range: lsp::Range::new(
15497                                        lsp::Position::new(5, 4),
15498                                        lsp::Position::new(5, 27),
15499                                    ),
15500                                    new_text: "".to_string(),
15501                                }],
15502                            )]
15503                            .into_iter()
15504                            .collect(),
15505                        ),
15506                        ..Default::default()
15507                    }),
15508                    ..Default::default()
15509                },
15510            )]))
15511        },
15512    );
15513    cx.update_editor(|editor, window, cx| {
15514        editor.toggle_code_actions(
15515            &ToggleCodeActions {
15516                deployed_from: None,
15517                quick_launch: false,
15518            },
15519            window,
15520            cx,
15521        );
15522    });
15523    code_action_requests.next().await;
15524    cx.run_until_parked();
15525    cx.condition(|editor, _| editor.context_menu_visible())
15526        .await;
15527    cx.update_editor(|editor, _, _| {
15528        assert!(
15529            !editor.hover_state.visible(),
15530            "Hover popover should be hidden when code action menu is shown"
15531        );
15532        // Hide code actions
15533        editor.context_menu.take();
15534    });
15535
15536    // Case 2: Test that code completions hide hover popover
15537    cx.dispatch_action(Hover);
15538    hover_requests.next().await;
15539    cx.condition(|editor, _| editor.hover_state.visible()).await;
15540    let counter = Arc::new(AtomicUsize::new(0));
15541    let mut completion_requests =
15542        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15543            let counter = counter.clone();
15544            async move {
15545                counter.fetch_add(1, atomic::Ordering::Release);
15546                Ok(Some(lsp::CompletionResponse::Array(vec![
15547                    lsp::CompletionItem {
15548                        label: "main".into(),
15549                        kind: Some(lsp::CompletionItemKind::FUNCTION),
15550                        detail: Some("() -> ()".to_string()),
15551                        ..Default::default()
15552                    },
15553                    lsp::CompletionItem {
15554                        label: "TestStruct".into(),
15555                        kind: Some(lsp::CompletionItemKind::STRUCT),
15556                        detail: Some("struct TestStruct".to_string()),
15557                        ..Default::default()
15558                    },
15559                ])))
15560            }
15561        });
15562    cx.update_editor(|editor, window, cx| {
15563        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15564    });
15565    completion_requests.next().await;
15566    cx.condition(|editor, _| editor.context_menu_visible())
15567        .await;
15568    cx.update_editor(|editor, _, _| {
15569        assert!(
15570            !editor.hover_state.visible(),
15571            "Hover popover should be hidden when completion menu is shown"
15572        );
15573    });
15574}
15575
15576#[gpui::test]
15577async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
15578    init_test(cx, |_| {});
15579
15580    let mut cx = EditorLspTestContext::new_rust(
15581        lsp::ServerCapabilities {
15582            completion_provider: Some(lsp::CompletionOptions {
15583                trigger_characters: Some(vec![".".to_string()]),
15584                resolve_provider: Some(true),
15585                ..Default::default()
15586            }),
15587            ..Default::default()
15588        },
15589        cx,
15590    )
15591    .await;
15592
15593    cx.set_state("fn main() { let a = 2ˇ; }");
15594    cx.simulate_keystroke(".");
15595
15596    let unresolved_item_1 = lsp::CompletionItem {
15597        label: "id".to_string(),
15598        filter_text: Some("id".to_string()),
15599        detail: None,
15600        documentation: None,
15601        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15602            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15603            new_text: ".id".to_string(),
15604        })),
15605        ..lsp::CompletionItem::default()
15606    };
15607    let resolved_item_1 = lsp::CompletionItem {
15608        additional_text_edits: Some(vec![lsp::TextEdit {
15609            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15610            new_text: "!!".to_string(),
15611        }]),
15612        ..unresolved_item_1.clone()
15613    };
15614    let unresolved_item_2 = lsp::CompletionItem {
15615        label: "other".to_string(),
15616        filter_text: Some("other".to_string()),
15617        detail: None,
15618        documentation: None,
15619        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15620            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15621            new_text: ".other".to_string(),
15622        })),
15623        ..lsp::CompletionItem::default()
15624    };
15625    let resolved_item_2 = lsp::CompletionItem {
15626        additional_text_edits: Some(vec![lsp::TextEdit {
15627            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
15628            new_text: "??".to_string(),
15629        }]),
15630        ..unresolved_item_2.clone()
15631    };
15632
15633    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
15634    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
15635    cx.lsp
15636        .server
15637        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15638            let unresolved_item_1 = unresolved_item_1.clone();
15639            let resolved_item_1 = resolved_item_1.clone();
15640            let unresolved_item_2 = unresolved_item_2.clone();
15641            let resolved_item_2 = resolved_item_2.clone();
15642            let resolve_requests_1 = resolve_requests_1.clone();
15643            let resolve_requests_2 = resolve_requests_2.clone();
15644            move |unresolved_request, _| {
15645                let unresolved_item_1 = unresolved_item_1.clone();
15646                let resolved_item_1 = resolved_item_1.clone();
15647                let unresolved_item_2 = unresolved_item_2.clone();
15648                let resolved_item_2 = resolved_item_2.clone();
15649                let resolve_requests_1 = resolve_requests_1.clone();
15650                let resolve_requests_2 = resolve_requests_2.clone();
15651                async move {
15652                    if unresolved_request == unresolved_item_1 {
15653                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
15654                        Ok(resolved_item_1.clone())
15655                    } else if unresolved_request == unresolved_item_2 {
15656                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
15657                        Ok(resolved_item_2.clone())
15658                    } else {
15659                        panic!("Unexpected completion item {unresolved_request:?}")
15660                    }
15661                }
15662            }
15663        })
15664        .detach();
15665
15666    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15667        let unresolved_item_1 = unresolved_item_1.clone();
15668        let unresolved_item_2 = unresolved_item_2.clone();
15669        async move {
15670            Ok(Some(lsp::CompletionResponse::Array(vec![
15671                unresolved_item_1,
15672                unresolved_item_2,
15673            ])))
15674        }
15675    })
15676    .next()
15677    .await;
15678
15679    cx.condition(|editor, _| editor.context_menu_visible())
15680        .await;
15681    cx.update_editor(|editor, _, _| {
15682        let context_menu = editor.context_menu.borrow_mut();
15683        let context_menu = context_menu
15684            .as_ref()
15685            .expect("Should have the context menu deployed");
15686        match context_menu {
15687            CodeContextMenu::Completions(completions_menu) => {
15688                let completions = completions_menu.completions.borrow_mut();
15689                assert_eq!(
15690                    completions
15691                        .iter()
15692                        .map(|completion| &completion.label.text)
15693                        .collect::<Vec<_>>(),
15694                    vec!["id", "other"]
15695                )
15696            }
15697            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
15698        }
15699    });
15700    cx.run_until_parked();
15701
15702    cx.update_editor(|editor, window, cx| {
15703        editor.context_menu_next(&ContextMenuNext, window, cx);
15704    });
15705    cx.run_until_parked();
15706    cx.update_editor(|editor, window, cx| {
15707        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15708    });
15709    cx.run_until_parked();
15710    cx.update_editor(|editor, window, cx| {
15711        editor.context_menu_next(&ContextMenuNext, window, cx);
15712    });
15713    cx.run_until_parked();
15714    cx.update_editor(|editor, window, cx| {
15715        editor
15716            .compose_completion(&ComposeCompletion::default(), window, cx)
15717            .expect("No task returned")
15718    })
15719    .await
15720    .expect("Completion failed");
15721    cx.run_until_parked();
15722
15723    cx.update_editor(|editor, _, cx| {
15724        assert_eq!(
15725            resolve_requests_1.load(atomic::Ordering::Acquire),
15726            1,
15727            "Should always resolve once despite multiple selections"
15728        );
15729        assert_eq!(
15730            resolve_requests_2.load(atomic::Ordering::Acquire),
15731            1,
15732            "Should always resolve once after multiple selections and applying the completion"
15733        );
15734        assert_eq!(
15735            editor.text(cx),
15736            "fn main() { let a = ??.other; }",
15737            "Should use resolved data when applying the completion"
15738        );
15739    });
15740}
15741
15742#[gpui::test]
15743async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
15744    init_test(cx, |_| {});
15745
15746    let item_0 = lsp::CompletionItem {
15747        label: "abs".into(),
15748        insert_text: Some("abs".into()),
15749        data: Some(json!({ "very": "special"})),
15750        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
15751        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
15752            lsp::InsertReplaceEdit {
15753                new_text: "abs".to_string(),
15754                insert: lsp::Range::default(),
15755                replace: lsp::Range::default(),
15756            },
15757        )),
15758        ..lsp::CompletionItem::default()
15759    };
15760    let items = iter::once(item_0.clone())
15761        .chain((11..51).map(|i| lsp::CompletionItem {
15762            label: format!("item_{}", i),
15763            insert_text: Some(format!("item_{}", i)),
15764            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15765            ..lsp::CompletionItem::default()
15766        }))
15767        .collect::<Vec<_>>();
15768
15769    let default_commit_characters = vec!["?".to_string()];
15770    let default_data = json!({ "default": "data"});
15771    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
15772    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
15773    let default_edit_range = lsp::Range {
15774        start: lsp::Position {
15775            line: 0,
15776            character: 5,
15777        },
15778        end: lsp::Position {
15779            line: 0,
15780            character: 5,
15781        },
15782    };
15783
15784    let mut cx = EditorLspTestContext::new_rust(
15785        lsp::ServerCapabilities {
15786            completion_provider: Some(lsp::CompletionOptions {
15787                trigger_characters: Some(vec![".".to_string()]),
15788                resolve_provider: Some(true),
15789                ..Default::default()
15790            }),
15791            ..Default::default()
15792        },
15793        cx,
15794    )
15795    .await;
15796
15797    cx.set_state("fn main() { let a = 2ˇ; }");
15798    cx.simulate_keystroke(".");
15799
15800    let completion_data = default_data.clone();
15801    let completion_characters = default_commit_characters.clone();
15802    let completion_items = items.clone();
15803    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15804        let default_data = completion_data.clone();
15805        let default_commit_characters = completion_characters.clone();
15806        let items = completion_items.clone();
15807        async move {
15808            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15809                items,
15810                item_defaults: Some(lsp::CompletionListItemDefaults {
15811                    data: Some(default_data.clone()),
15812                    commit_characters: Some(default_commit_characters.clone()),
15813                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
15814                        default_edit_range,
15815                    )),
15816                    insert_text_format: Some(default_insert_text_format),
15817                    insert_text_mode: Some(default_insert_text_mode),
15818                }),
15819                ..lsp::CompletionList::default()
15820            })))
15821        }
15822    })
15823    .next()
15824    .await;
15825
15826    let resolved_items = Arc::new(Mutex::new(Vec::new()));
15827    cx.lsp
15828        .server
15829        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
15830            let closure_resolved_items = resolved_items.clone();
15831            move |item_to_resolve, _| {
15832                let closure_resolved_items = closure_resolved_items.clone();
15833                async move {
15834                    closure_resolved_items.lock().push(item_to_resolve.clone());
15835                    Ok(item_to_resolve)
15836                }
15837            }
15838        })
15839        .detach();
15840
15841    cx.condition(|editor, _| editor.context_menu_visible())
15842        .await;
15843    cx.run_until_parked();
15844    cx.update_editor(|editor, _, _| {
15845        let menu = editor.context_menu.borrow_mut();
15846        match menu.as_ref().expect("should have the completions menu") {
15847            CodeContextMenu::Completions(completions_menu) => {
15848                assert_eq!(
15849                    completions_menu
15850                        .entries
15851                        .borrow()
15852                        .iter()
15853                        .map(|mat| mat.string.clone())
15854                        .collect::<Vec<String>>(),
15855                    items
15856                        .iter()
15857                        .map(|completion| completion.label.clone())
15858                        .collect::<Vec<String>>()
15859                );
15860            }
15861            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
15862        }
15863    });
15864    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
15865    // with 4 from the end.
15866    assert_eq!(
15867        *resolved_items.lock(),
15868        [&items[0..16], &items[items.len() - 4..items.len()]]
15869            .concat()
15870            .iter()
15871            .cloned()
15872            .map(|mut item| {
15873                if item.data.is_none() {
15874                    item.data = Some(default_data.clone());
15875                }
15876                item
15877            })
15878            .collect::<Vec<lsp::CompletionItem>>(),
15879        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
15880    );
15881    resolved_items.lock().clear();
15882
15883    cx.update_editor(|editor, window, cx| {
15884        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
15885    });
15886    cx.run_until_parked();
15887    // Completions that have already been resolved are skipped.
15888    assert_eq!(
15889        *resolved_items.lock(),
15890        items[items.len() - 17..items.len() - 4]
15891            .iter()
15892            .cloned()
15893            .map(|mut item| {
15894                if item.data.is_none() {
15895                    item.data = Some(default_data.clone());
15896                }
15897                item
15898            })
15899            .collect::<Vec<lsp::CompletionItem>>()
15900    );
15901    resolved_items.lock().clear();
15902}
15903
15904#[gpui::test]
15905async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
15906    init_test(cx, |_| {});
15907
15908    let mut cx = EditorLspTestContext::new(
15909        Language::new(
15910            LanguageConfig {
15911                matcher: LanguageMatcher {
15912                    path_suffixes: vec!["jsx".into()],
15913                    ..Default::default()
15914                },
15915                overrides: [(
15916                    "element".into(),
15917                    LanguageConfigOverride {
15918                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
15919                        ..Default::default()
15920                    },
15921                )]
15922                .into_iter()
15923                .collect(),
15924                ..Default::default()
15925            },
15926            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15927        )
15928        .with_override_query("(jsx_self_closing_element) @element")
15929        .unwrap(),
15930        lsp::ServerCapabilities {
15931            completion_provider: Some(lsp::CompletionOptions {
15932                trigger_characters: Some(vec![":".to_string()]),
15933                ..Default::default()
15934            }),
15935            ..Default::default()
15936        },
15937        cx,
15938    )
15939    .await;
15940
15941    cx.lsp
15942        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15943            Ok(Some(lsp::CompletionResponse::Array(vec![
15944                lsp::CompletionItem {
15945                    label: "bg-blue".into(),
15946                    ..Default::default()
15947                },
15948                lsp::CompletionItem {
15949                    label: "bg-red".into(),
15950                    ..Default::default()
15951                },
15952                lsp::CompletionItem {
15953                    label: "bg-yellow".into(),
15954                    ..Default::default()
15955                },
15956            ])))
15957        });
15958
15959    cx.set_state(r#"<p class="bgˇ" />"#);
15960
15961    // Trigger completion when typing a dash, because the dash is an extra
15962    // word character in the 'element' scope, which contains the cursor.
15963    cx.simulate_keystroke("-");
15964    cx.executor().run_until_parked();
15965    cx.update_editor(|editor, _, _| {
15966        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15967        {
15968            assert_eq!(
15969                completion_menu_entries(&menu),
15970                &["bg-blue", "bg-red", "bg-yellow"]
15971            );
15972        } else {
15973            panic!("expected completion menu to be open");
15974        }
15975    });
15976
15977    cx.simulate_keystroke("l");
15978    cx.executor().run_until_parked();
15979    cx.update_editor(|editor, _, _| {
15980        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15981        {
15982            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
15983        } else {
15984            panic!("expected completion menu to be open");
15985        }
15986    });
15987
15988    // When filtering completions, consider the character after the '-' to
15989    // be the start of a subword.
15990    cx.set_state(r#"<p class="yelˇ" />"#);
15991    cx.simulate_keystroke("l");
15992    cx.executor().run_until_parked();
15993    cx.update_editor(|editor, _, _| {
15994        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15995        {
15996            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
15997        } else {
15998            panic!("expected completion menu to be open");
15999        }
16000    });
16001}
16002
16003fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16004    let entries = menu.entries.borrow();
16005    entries.iter().map(|mat| mat.string.clone()).collect()
16006}
16007
16008#[gpui::test]
16009async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16010    init_test(cx, |settings| {
16011        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16012            Formatter::Prettier,
16013        )))
16014    });
16015
16016    let fs = FakeFs::new(cx.executor());
16017    fs.insert_file(path!("/file.ts"), Default::default()).await;
16018
16019    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16020    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16021
16022    language_registry.add(Arc::new(Language::new(
16023        LanguageConfig {
16024            name: "TypeScript".into(),
16025            matcher: LanguageMatcher {
16026                path_suffixes: vec!["ts".to_string()],
16027                ..Default::default()
16028            },
16029            ..Default::default()
16030        },
16031        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16032    )));
16033    update_test_language_settings(cx, |settings| {
16034        settings.defaults.prettier = Some(PrettierSettings {
16035            allowed: true,
16036            ..PrettierSettings::default()
16037        });
16038    });
16039
16040    let test_plugin = "test_plugin";
16041    let _ = language_registry.register_fake_lsp(
16042        "TypeScript",
16043        FakeLspAdapter {
16044            prettier_plugins: vec![test_plugin],
16045            ..Default::default()
16046        },
16047    );
16048
16049    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16050    let buffer = project
16051        .update(cx, |project, cx| {
16052            project.open_local_buffer(path!("/file.ts"), cx)
16053        })
16054        .await
16055        .unwrap();
16056
16057    let buffer_text = "one\ntwo\nthree\n";
16058    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16059    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16060    editor.update_in(cx, |editor, window, cx| {
16061        editor.set_text(buffer_text, window, cx)
16062    });
16063
16064    editor
16065        .update_in(cx, |editor, window, cx| {
16066            editor.perform_format(
16067                project.clone(),
16068                FormatTrigger::Manual,
16069                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16070                window,
16071                cx,
16072            )
16073        })
16074        .unwrap()
16075        .await;
16076    assert_eq!(
16077        editor.update(cx, |editor, cx| editor.text(cx)),
16078        buffer_text.to_string() + prettier_format_suffix,
16079        "Test prettier formatting was not applied to the original buffer text",
16080    );
16081
16082    update_test_language_settings(cx, |settings| {
16083        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16084    });
16085    let format = editor.update_in(cx, |editor, window, cx| {
16086        editor.perform_format(
16087            project.clone(),
16088            FormatTrigger::Manual,
16089            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16090            window,
16091            cx,
16092        )
16093    });
16094    format.await.unwrap();
16095    assert_eq!(
16096        editor.update(cx, |editor, cx| editor.text(cx)),
16097        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16098        "Autoformatting (via test prettier) was not applied to the original buffer text",
16099    );
16100}
16101
16102#[gpui::test]
16103async fn test_addition_reverts(cx: &mut TestAppContext) {
16104    init_test(cx, |_| {});
16105    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16106    let base_text = indoc! {r#"
16107        struct Row;
16108        struct Row1;
16109        struct Row2;
16110
16111        struct Row4;
16112        struct Row5;
16113        struct Row6;
16114
16115        struct Row8;
16116        struct Row9;
16117        struct Row10;"#};
16118
16119    // When addition hunks are not adjacent to carets, no hunk revert is performed
16120    assert_hunk_revert(
16121        indoc! {r#"struct Row;
16122                   struct Row1;
16123                   struct Row1.1;
16124                   struct Row1.2;
16125                   struct Row2;ˇ
16126
16127                   struct Row4;
16128                   struct Row5;
16129                   struct Row6;
16130
16131                   struct Row8;
16132                   ˇstruct Row9;
16133                   struct Row9.1;
16134                   struct Row9.2;
16135                   struct Row9.3;
16136                   struct Row10;"#},
16137        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16138        indoc! {r#"struct Row;
16139                   struct Row1;
16140                   struct Row1.1;
16141                   struct Row1.2;
16142                   struct Row2;ˇ
16143
16144                   struct Row4;
16145                   struct Row5;
16146                   struct Row6;
16147
16148                   struct Row8;
16149                   ˇstruct Row9;
16150                   struct Row9.1;
16151                   struct Row9.2;
16152                   struct Row9.3;
16153                   struct Row10;"#},
16154        base_text,
16155        &mut cx,
16156    );
16157    // Same for selections
16158    assert_hunk_revert(
16159        indoc! {r#"struct Row;
16160                   struct Row1;
16161                   struct Row2;
16162                   struct Row2.1;
16163                   struct Row2.2;
16164                   «ˇ
16165                   struct Row4;
16166                   struct» Row5;
16167                   «struct Row6;
16168                   ˇ»
16169                   struct Row9.1;
16170                   struct Row9.2;
16171                   struct Row9.3;
16172                   struct Row8;
16173                   struct Row9;
16174                   struct Row10;"#},
16175        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16176        indoc! {r#"struct Row;
16177                   struct Row1;
16178                   struct Row2;
16179                   struct Row2.1;
16180                   struct Row2.2;
16181                   «ˇ
16182                   struct Row4;
16183                   struct» Row5;
16184                   «struct Row6;
16185                   ˇ»
16186                   struct Row9.1;
16187                   struct Row9.2;
16188                   struct Row9.3;
16189                   struct Row8;
16190                   struct Row9;
16191                   struct Row10;"#},
16192        base_text,
16193        &mut cx,
16194    );
16195
16196    // When carets and selections intersect the addition hunks, those are reverted.
16197    // Adjacent carets got merged.
16198    assert_hunk_revert(
16199        indoc! {r#"struct Row;
16200                   ˇ// something on the top
16201                   struct Row1;
16202                   struct Row2;
16203                   struct Roˇw3.1;
16204                   struct Row2.2;
16205                   struct Row2.3;ˇ
16206
16207                   struct Row4;
16208                   struct ˇRow5.1;
16209                   struct Row5.2;
16210                   struct «Rowˇ»5.3;
16211                   struct Row5;
16212                   struct Row6;
16213                   ˇ
16214                   struct Row9.1;
16215                   struct «Rowˇ»9.2;
16216                   struct «ˇRow»9.3;
16217                   struct Row8;
16218                   struct Row9;
16219                   «ˇ// something on bottom»
16220                   struct Row10;"#},
16221        vec![
16222            DiffHunkStatusKind::Added,
16223            DiffHunkStatusKind::Added,
16224            DiffHunkStatusKind::Added,
16225            DiffHunkStatusKind::Added,
16226            DiffHunkStatusKind::Added,
16227        ],
16228        indoc! {r#"struct Row;
16229                   ˇstruct Row1;
16230                   struct Row2;
16231                   ˇ
16232                   struct Row4;
16233                   ˇstruct Row5;
16234                   struct Row6;
16235                   ˇ
16236                   ˇstruct Row8;
16237                   struct Row9;
16238                   ˇstruct Row10;"#},
16239        base_text,
16240        &mut cx,
16241    );
16242}
16243
16244#[gpui::test]
16245async fn test_modification_reverts(cx: &mut TestAppContext) {
16246    init_test(cx, |_| {});
16247    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16248    let base_text = indoc! {r#"
16249        struct Row;
16250        struct Row1;
16251        struct Row2;
16252
16253        struct Row4;
16254        struct Row5;
16255        struct Row6;
16256
16257        struct Row8;
16258        struct Row9;
16259        struct Row10;"#};
16260
16261    // Modification hunks behave the same as the addition ones.
16262    assert_hunk_revert(
16263        indoc! {r#"struct Row;
16264                   struct Row1;
16265                   struct Row33;
16266                   ˇ
16267                   struct Row4;
16268                   struct Row5;
16269                   struct Row6;
16270                   ˇ
16271                   struct Row99;
16272                   struct Row9;
16273                   struct Row10;"#},
16274        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16275        indoc! {r#"struct Row;
16276                   struct Row1;
16277                   struct Row33;
16278                   ˇ
16279                   struct Row4;
16280                   struct Row5;
16281                   struct Row6;
16282                   ˇ
16283                   struct Row99;
16284                   struct Row9;
16285                   struct Row10;"#},
16286        base_text,
16287        &mut cx,
16288    );
16289    assert_hunk_revert(
16290        indoc! {r#"struct Row;
16291                   struct Row1;
16292                   struct Row33;
16293                   «ˇ
16294                   struct Row4;
16295                   struct» Row5;
16296                   «struct Row6;
16297                   ˇ»
16298                   struct Row99;
16299                   struct Row9;
16300                   struct Row10;"#},
16301        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
16302        indoc! {r#"struct Row;
16303                   struct Row1;
16304                   struct Row33;
16305                   «ˇ
16306                   struct Row4;
16307                   struct» Row5;
16308                   «struct Row6;
16309                   ˇ»
16310                   struct Row99;
16311                   struct Row9;
16312                   struct Row10;"#},
16313        base_text,
16314        &mut cx,
16315    );
16316
16317    assert_hunk_revert(
16318        indoc! {r#"ˇstruct Row1.1;
16319                   struct Row1;
16320                   «ˇstr»uct Row22;
16321
16322                   struct ˇRow44;
16323                   struct Row5;
16324                   struct «Rˇ»ow66;ˇ
16325
16326                   «struˇ»ct Row88;
16327                   struct Row9;
16328                   struct Row1011;ˇ"#},
16329        vec![
16330            DiffHunkStatusKind::Modified,
16331            DiffHunkStatusKind::Modified,
16332            DiffHunkStatusKind::Modified,
16333            DiffHunkStatusKind::Modified,
16334            DiffHunkStatusKind::Modified,
16335            DiffHunkStatusKind::Modified,
16336        ],
16337        indoc! {r#"struct Row;
16338                   ˇstruct Row1;
16339                   struct Row2;
16340                   ˇ
16341                   struct Row4;
16342                   ˇstruct Row5;
16343                   struct Row6;
16344                   ˇ
16345                   struct Row8;
16346                   ˇstruct Row9;
16347                   struct Row10;ˇ"#},
16348        base_text,
16349        &mut cx,
16350    );
16351}
16352
16353#[gpui::test]
16354async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
16355    init_test(cx, |_| {});
16356    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16357    let base_text = indoc! {r#"
16358        one
16359
16360        two
16361        three
16362        "#};
16363
16364    cx.set_head_text(base_text);
16365    cx.set_state("\nˇ\n");
16366    cx.executor().run_until_parked();
16367    cx.update_editor(|editor, _window, cx| {
16368        editor.expand_selected_diff_hunks(cx);
16369    });
16370    cx.executor().run_until_parked();
16371    cx.update_editor(|editor, window, cx| {
16372        editor.backspace(&Default::default(), window, cx);
16373    });
16374    cx.run_until_parked();
16375    cx.assert_state_with_diff(
16376        indoc! {r#"
16377
16378        - two
16379        - threeˇ
16380        +
16381        "#}
16382        .to_string(),
16383    );
16384}
16385
16386#[gpui::test]
16387async fn test_deletion_reverts(cx: &mut TestAppContext) {
16388    init_test(cx, |_| {});
16389    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16390    let base_text = indoc! {r#"struct Row;
16391struct Row1;
16392struct Row2;
16393
16394struct Row4;
16395struct Row5;
16396struct Row6;
16397
16398struct Row8;
16399struct Row9;
16400struct Row10;"#};
16401
16402    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
16403    assert_hunk_revert(
16404        indoc! {r#"struct Row;
16405                   struct Row2;
16406
16407                   ˇstruct Row4;
16408                   struct Row5;
16409                   struct Row6;
16410                   ˇ
16411                   struct Row8;
16412                   struct Row10;"#},
16413        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16414        indoc! {r#"struct Row;
16415                   struct Row2;
16416
16417                   ˇstruct Row4;
16418                   struct Row5;
16419                   struct Row6;
16420                   ˇ
16421                   struct Row8;
16422                   struct Row10;"#},
16423        base_text,
16424        &mut cx,
16425    );
16426    assert_hunk_revert(
16427        indoc! {r#"struct Row;
16428                   struct Row2;
16429
16430                   «ˇstruct Row4;
16431                   struct» Row5;
16432                   «struct Row6;
16433                   ˇ»
16434                   struct Row8;
16435                   struct Row10;"#},
16436        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16437        indoc! {r#"struct Row;
16438                   struct Row2;
16439
16440                   «ˇstruct Row4;
16441                   struct» Row5;
16442                   «struct Row6;
16443                   ˇ»
16444                   struct Row8;
16445                   struct Row10;"#},
16446        base_text,
16447        &mut cx,
16448    );
16449
16450    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
16451    assert_hunk_revert(
16452        indoc! {r#"struct Row;
16453                   ˇstruct Row2;
16454
16455                   struct Row4;
16456                   struct Row5;
16457                   struct Row6;
16458
16459                   struct Row8;ˇ
16460                   struct Row10;"#},
16461        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
16462        indoc! {r#"struct Row;
16463                   struct Row1;
16464                   ˇstruct Row2;
16465
16466                   struct Row4;
16467                   struct Row5;
16468                   struct Row6;
16469
16470                   struct Row8;ˇ
16471                   struct Row9;
16472                   struct Row10;"#},
16473        base_text,
16474        &mut cx,
16475    );
16476    assert_hunk_revert(
16477        indoc! {r#"struct Row;
16478                   struct Row2«ˇ;
16479                   struct Row4;
16480                   struct» Row5;
16481                   «struct Row6;
16482
16483                   struct Row8;ˇ»
16484                   struct Row10;"#},
16485        vec![
16486            DiffHunkStatusKind::Deleted,
16487            DiffHunkStatusKind::Deleted,
16488            DiffHunkStatusKind::Deleted,
16489        ],
16490        indoc! {r#"struct Row;
16491                   struct Row1;
16492                   struct Row2«ˇ;
16493
16494                   struct Row4;
16495                   struct» Row5;
16496                   «struct Row6;
16497
16498                   struct Row8;ˇ»
16499                   struct Row9;
16500                   struct Row10;"#},
16501        base_text,
16502        &mut cx,
16503    );
16504}
16505
16506#[gpui::test]
16507async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
16508    init_test(cx, |_| {});
16509
16510    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
16511    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
16512    let base_text_3 =
16513        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
16514
16515    let text_1 = edit_first_char_of_every_line(base_text_1);
16516    let text_2 = edit_first_char_of_every_line(base_text_2);
16517    let text_3 = edit_first_char_of_every_line(base_text_3);
16518
16519    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
16520    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
16521    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
16522
16523    let multibuffer = cx.new(|cx| {
16524        let mut multibuffer = MultiBuffer::new(ReadWrite);
16525        multibuffer.push_excerpts(
16526            buffer_1.clone(),
16527            [
16528                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16529                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16530                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16531            ],
16532            cx,
16533        );
16534        multibuffer.push_excerpts(
16535            buffer_2.clone(),
16536            [
16537                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16538                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16539                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16540            ],
16541            cx,
16542        );
16543        multibuffer.push_excerpts(
16544            buffer_3.clone(),
16545            [
16546                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16547                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16548                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16549            ],
16550            cx,
16551        );
16552        multibuffer
16553    });
16554
16555    let fs = FakeFs::new(cx.executor());
16556    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
16557    let (editor, cx) = cx
16558        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
16559    editor.update_in(cx, |editor, _window, cx| {
16560        for (buffer, diff_base) in [
16561            (buffer_1.clone(), base_text_1),
16562            (buffer_2.clone(), base_text_2),
16563            (buffer_3.clone(), base_text_3),
16564        ] {
16565            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
16566            editor
16567                .buffer
16568                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
16569        }
16570    });
16571    cx.executor().run_until_parked();
16572
16573    editor.update_in(cx, |editor, window, cx| {
16574        assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
16575        editor.select_all(&SelectAll, window, cx);
16576        editor.git_restore(&Default::default(), window, cx);
16577    });
16578    cx.executor().run_until_parked();
16579
16580    // When all ranges are selected, all buffer hunks are reverted.
16581    editor.update(cx, |editor, cx| {
16582        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");
16583    });
16584    buffer_1.update(cx, |buffer, _| {
16585        assert_eq!(buffer.text(), base_text_1);
16586    });
16587    buffer_2.update(cx, |buffer, _| {
16588        assert_eq!(buffer.text(), base_text_2);
16589    });
16590    buffer_3.update(cx, |buffer, _| {
16591        assert_eq!(buffer.text(), base_text_3);
16592    });
16593
16594    editor.update_in(cx, |editor, window, cx| {
16595        editor.undo(&Default::default(), window, cx);
16596    });
16597
16598    editor.update_in(cx, |editor, window, cx| {
16599        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16600            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
16601        });
16602        editor.git_restore(&Default::default(), window, cx);
16603    });
16604
16605    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
16606    // but not affect buffer_2 and its related excerpts.
16607    editor.update(cx, |editor, cx| {
16608        assert_eq!(
16609            editor.text(cx),
16610            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
16611        );
16612    });
16613    buffer_1.update(cx, |buffer, _| {
16614        assert_eq!(buffer.text(), base_text_1);
16615    });
16616    buffer_2.update(cx, |buffer, _| {
16617        assert_eq!(
16618            buffer.text(),
16619            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
16620        );
16621    });
16622    buffer_3.update(cx, |buffer, _| {
16623        assert_eq!(
16624            buffer.text(),
16625            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
16626        );
16627    });
16628
16629    fn edit_first_char_of_every_line(text: &str) -> String {
16630        text.split('\n')
16631            .map(|line| format!("X{}", &line[1..]))
16632            .collect::<Vec<_>>()
16633            .join("\n")
16634    }
16635}
16636
16637#[gpui::test]
16638async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
16639    init_test(cx, |_| {});
16640
16641    let cols = 4;
16642    let rows = 10;
16643    let sample_text_1 = sample_text(rows, cols, 'a');
16644    assert_eq!(
16645        sample_text_1,
16646        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
16647    );
16648    let sample_text_2 = sample_text(rows, cols, 'l');
16649    assert_eq!(
16650        sample_text_2,
16651        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
16652    );
16653    let sample_text_3 = sample_text(rows, cols, 'v');
16654    assert_eq!(
16655        sample_text_3,
16656        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
16657    );
16658
16659    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
16660    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
16661    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
16662
16663    let multi_buffer = cx.new(|cx| {
16664        let mut multibuffer = MultiBuffer::new(ReadWrite);
16665        multibuffer.push_excerpts(
16666            buffer_1.clone(),
16667            [
16668                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16669                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16670                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16671            ],
16672            cx,
16673        );
16674        multibuffer.push_excerpts(
16675            buffer_2.clone(),
16676            [
16677                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16678                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16679                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16680            ],
16681            cx,
16682        );
16683        multibuffer.push_excerpts(
16684            buffer_3.clone(),
16685            [
16686                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
16687                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
16688                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
16689            ],
16690            cx,
16691        );
16692        multibuffer
16693    });
16694
16695    let fs = FakeFs::new(cx.executor());
16696    fs.insert_tree(
16697        "/a",
16698        json!({
16699            "main.rs": sample_text_1,
16700            "other.rs": sample_text_2,
16701            "lib.rs": sample_text_3,
16702        }),
16703    )
16704    .await;
16705    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16706    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16707    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16708    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16709        Editor::new(
16710            EditorMode::full(),
16711            multi_buffer,
16712            Some(project.clone()),
16713            window,
16714            cx,
16715        )
16716    });
16717    let multibuffer_item_id = workspace
16718        .update(cx, |workspace, window, cx| {
16719            assert!(
16720                workspace.active_item(cx).is_none(),
16721                "active item should be None before the first item is added"
16722            );
16723            workspace.add_item_to_active_pane(
16724                Box::new(multi_buffer_editor.clone()),
16725                None,
16726                true,
16727                window,
16728                cx,
16729            );
16730            let active_item = workspace
16731                .active_item(cx)
16732                .expect("should have an active item after adding the multi buffer");
16733            assert!(
16734                !active_item.is_singleton(cx),
16735                "A multi buffer was expected to active after adding"
16736            );
16737            active_item.item_id()
16738        })
16739        .unwrap();
16740    cx.executor().run_until_parked();
16741
16742    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16743        editor.change_selections(
16744            SelectionEffects::scroll(Autoscroll::Next),
16745            window,
16746            cx,
16747            |s| s.select_ranges(Some(1..2)),
16748        );
16749        editor.open_excerpts(&OpenExcerpts, window, cx);
16750    });
16751    cx.executor().run_until_parked();
16752    let first_item_id = workspace
16753        .update(cx, |workspace, window, cx| {
16754            let active_item = workspace
16755                .active_item(cx)
16756                .expect("should have an active item after navigating into the 1st buffer");
16757            let first_item_id = active_item.item_id();
16758            assert_ne!(
16759                first_item_id, multibuffer_item_id,
16760                "Should navigate into the 1st buffer and activate it"
16761            );
16762            assert!(
16763                active_item.is_singleton(cx),
16764                "New active item should be a singleton buffer"
16765            );
16766            assert_eq!(
16767                active_item
16768                    .act_as::<Editor>(cx)
16769                    .expect("should have navigated into an editor for the 1st buffer")
16770                    .read(cx)
16771                    .text(cx),
16772                sample_text_1
16773            );
16774
16775            workspace
16776                .go_back(workspace.active_pane().downgrade(), window, cx)
16777                .detach_and_log_err(cx);
16778
16779            first_item_id
16780        })
16781        .unwrap();
16782    cx.executor().run_until_parked();
16783    workspace
16784        .update(cx, |workspace, _, cx| {
16785            let active_item = workspace
16786                .active_item(cx)
16787                .expect("should have an active item after navigating back");
16788            assert_eq!(
16789                active_item.item_id(),
16790                multibuffer_item_id,
16791                "Should navigate back to the multi buffer"
16792            );
16793            assert!(!active_item.is_singleton(cx));
16794        })
16795        .unwrap();
16796
16797    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16798        editor.change_selections(
16799            SelectionEffects::scroll(Autoscroll::Next),
16800            window,
16801            cx,
16802            |s| s.select_ranges(Some(39..40)),
16803        );
16804        editor.open_excerpts(&OpenExcerpts, window, cx);
16805    });
16806    cx.executor().run_until_parked();
16807    let second_item_id = workspace
16808        .update(cx, |workspace, window, cx| {
16809            let active_item = workspace
16810                .active_item(cx)
16811                .expect("should have an active item after navigating into the 2nd buffer");
16812            let second_item_id = active_item.item_id();
16813            assert_ne!(
16814                second_item_id, multibuffer_item_id,
16815                "Should navigate away from the multibuffer"
16816            );
16817            assert_ne!(
16818                second_item_id, first_item_id,
16819                "Should navigate into the 2nd buffer and activate it"
16820            );
16821            assert!(
16822                active_item.is_singleton(cx),
16823                "New active item should be a singleton buffer"
16824            );
16825            assert_eq!(
16826                active_item
16827                    .act_as::<Editor>(cx)
16828                    .expect("should have navigated into an editor")
16829                    .read(cx)
16830                    .text(cx),
16831                sample_text_2
16832            );
16833
16834            workspace
16835                .go_back(workspace.active_pane().downgrade(), window, cx)
16836                .detach_and_log_err(cx);
16837
16838            second_item_id
16839        })
16840        .unwrap();
16841    cx.executor().run_until_parked();
16842    workspace
16843        .update(cx, |workspace, _, cx| {
16844            let active_item = workspace
16845                .active_item(cx)
16846                .expect("should have an active item after navigating back from the 2nd buffer");
16847            assert_eq!(
16848                active_item.item_id(),
16849                multibuffer_item_id,
16850                "Should navigate back from the 2nd buffer to the multi buffer"
16851            );
16852            assert!(!active_item.is_singleton(cx));
16853        })
16854        .unwrap();
16855
16856    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16857        editor.change_selections(
16858            SelectionEffects::scroll(Autoscroll::Next),
16859            window,
16860            cx,
16861            |s| s.select_ranges(Some(70..70)),
16862        );
16863        editor.open_excerpts(&OpenExcerpts, window, cx);
16864    });
16865    cx.executor().run_until_parked();
16866    workspace
16867        .update(cx, |workspace, window, cx| {
16868            let active_item = workspace
16869                .active_item(cx)
16870                .expect("should have an active item after navigating into the 3rd buffer");
16871            let third_item_id = active_item.item_id();
16872            assert_ne!(
16873                third_item_id, multibuffer_item_id,
16874                "Should navigate into the 3rd buffer and activate it"
16875            );
16876            assert_ne!(third_item_id, first_item_id);
16877            assert_ne!(third_item_id, second_item_id);
16878            assert!(
16879                active_item.is_singleton(cx),
16880                "New active item should be a singleton buffer"
16881            );
16882            assert_eq!(
16883                active_item
16884                    .act_as::<Editor>(cx)
16885                    .expect("should have navigated into an editor")
16886                    .read(cx)
16887                    .text(cx),
16888                sample_text_3
16889            );
16890
16891            workspace
16892                .go_back(workspace.active_pane().downgrade(), window, cx)
16893                .detach_and_log_err(cx);
16894        })
16895        .unwrap();
16896    cx.executor().run_until_parked();
16897    workspace
16898        .update(cx, |workspace, _, cx| {
16899            let active_item = workspace
16900                .active_item(cx)
16901                .expect("should have an active item after navigating back from the 3rd buffer");
16902            assert_eq!(
16903                active_item.item_id(),
16904                multibuffer_item_id,
16905                "Should navigate back from the 3rd buffer to the multi buffer"
16906            );
16907            assert!(!active_item.is_singleton(cx));
16908        })
16909        .unwrap();
16910}
16911
16912#[gpui::test]
16913async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16914    init_test(cx, |_| {});
16915
16916    let mut cx = EditorTestContext::new(cx).await;
16917
16918    let diff_base = r#"
16919        use some::mod;
16920
16921        const A: u32 = 42;
16922
16923        fn main() {
16924            println!("hello");
16925
16926            println!("world");
16927        }
16928        "#
16929    .unindent();
16930
16931    cx.set_state(
16932        &r#"
16933        use some::modified;
16934
16935        ˇ
16936        fn main() {
16937            println!("hello there");
16938
16939            println!("around the");
16940            println!("world");
16941        }
16942        "#
16943        .unindent(),
16944    );
16945
16946    cx.set_head_text(&diff_base);
16947    executor.run_until_parked();
16948
16949    cx.update_editor(|editor, window, cx| {
16950        editor.go_to_next_hunk(&GoToHunk, window, cx);
16951        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16952    });
16953    executor.run_until_parked();
16954    cx.assert_state_with_diff(
16955        r#"
16956          use some::modified;
16957
16958
16959          fn main() {
16960        -     println!("hello");
16961        + ˇ    println!("hello there");
16962
16963              println!("around the");
16964              println!("world");
16965          }
16966        "#
16967        .unindent(),
16968    );
16969
16970    cx.update_editor(|editor, window, cx| {
16971        for _ in 0..2 {
16972            editor.go_to_next_hunk(&GoToHunk, window, cx);
16973            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16974        }
16975    });
16976    executor.run_until_parked();
16977    cx.assert_state_with_diff(
16978        r#"
16979        - use some::mod;
16980        + ˇuse some::modified;
16981
16982
16983          fn main() {
16984        -     println!("hello");
16985        +     println!("hello there");
16986
16987        +     println!("around the");
16988              println!("world");
16989          }
16990        "#
16991        .unindent(),
16992    );
16993
16994    cx.update_editor(|editor, window, cx| {
16995        editor.go_to_next_hunk(&GoToHunk, window, cx);
16996        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
16997    });
16998    executor.run_until_parked();
16999    cx.assert_state_with_diff(
17000        r#"
17001        - use some::mod;
17002        + use some::modified;
17003
17004        - const A: u32 = 42;
17005          ˇ
17006          fn main() {
17007        -     println!("hello");
17008        +     println!("hello there");
17009
17010        +     println!("around the");
17011              println!("world");
17012          }
17013        "#
17014        .unindent(),
17015    );
17016
17017    cx.update_editor(|editor, window, cx| {
17018        editor.cancel(&Cancel, window, cx);
17019    });
17020
17021    cx.assert_state_with_diff(
17022        r#"
17023          use some::modified;
17024
17025          ˇ
17026          fn main() {
17027              println!("hello there");
17028
17029              println!("around the");
17030              println!("world");
17031          }
17032        "#
17033        .unindent(),
17034    );
17035}
17036
17037#[gpui::test]
17038async fn test_diff_base_change_with_expanded_diff_hunks(
17039    executor: BackgroundExecutor,
17040    cx: &mut TestAppContext,
17041) {
17042    init_test(cx, |_| {});
17043
17044    let mut cx = EditorTestContext::new(cx).await;
17045
17046    let diff_base = r#"
17047        use some::mod1;
17048        use some::mod2;
17049
17050        const A: u32 = 42;
17051        const B: u32 = 42;
17052        const C: u32 = 42;
17053
17054        fn main() {
17055            println!("hello");
17056
17057            println!("world");
17058        }
17059        "#
17060    .unindent();
17061
17062    cx.set_state(
17063        &r#"
17064        use some::mod2;
17065
17066        const A: u32 = 42;
17067        const C: u32 = 42;
17068
17069        fn main(ˇ) {
17070            //println!("hello");
17071
17072            println!("world");
17073            //
17074            //
17075        }
17076        "#
17077        .unindent(),
17078    );
17079
17080    cx.set_head_text(&diff_base);
17081    executor.run_until_parked();
17082
17083    cx.update_editor(|editor, window, cx| {
17084        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17085    });
17086    executor.run_until_parked();
17087    cx.assert_state_with_diff(
17088        r#"
17089        - use some::mod1;
17090          use some::mod2;
17091
17092          const A: u32 = 42;
17093        - const B: u32 = 42;
17094          const C: u32 = 42;
17095
17096          fn main(ˇ) {
17097        -     println!("hello");
17098        +     //println!("hello");
17099
17100              println!("world");
17101        +     //
17102        +     //
17103          }
17104        "#
17105        .unindent(),
17106    );
17107
17108    cx.set_head_text("new diff base!");
17109    executor.run_until_parked();
17110    cx.assert_state_with_diff(
17111        r#"
17112        - new diff base!
17113        + use some::mod2;
17114        +
17115        + const A: u32 = 42;
17116        + const C: u32 = 42;
17117        +
17118        + fn main(ˇ) {
17119        +     //println!("hello");
17120        +
17121        +     println!("world");
17122        +     //
17123        +     //
17124        + }
17125        "#
17126        .unindent(),
17127    );
17128}
17129
17130#[gpui::test]
17131async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17132    init_test(cx, |_| {});
17133
17134    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17135    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17136    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17137    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17138    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17139    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17140
17141    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17142    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17143    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17144
17145    let multi_buffer = cx.new(|cx| {
17146        let mut multibuffer = MultiBuffer::new(ReadWrite);
17147        multibuffer.push_excerpts(
17148            buffer_1.clone(),
17149            [
17150                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17151                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17152                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17153            ],
17154            cx,
17155        );
17156        multibuffer.push_excerpts(
17157            buffer_2.clone(),
17158            [
17159                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17160                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17161                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17162            ],
17163            cx,
17164        );
17165        multibuffer.push_excerpts(
17166            buffer_3.clone(),
17167            [
17168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17171            ],
17172            cx,
17173        );
17174        multibuffer
17175    });
17176
17177    let editor =
17178        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17179    editor
17180        .update(cx, |editor, _window, cx| {
17181            for (buffer, diff_base) in [
17182                (buffer_1.clone(), file_1_old),
17183                (buffer_2.clone(), file_2_old),
17184                (buffer_3.clone(), file_3_old),
17185            ] {
17186                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
17187                editor
17188                    .buffer
17189                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17190            }
17191        })
17192        .unwrap();
17193
17194    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17195    cx.run_until_parked();
17196
17197    cx.assert_editor_state(
17198        &"
17199            ˇaaa
17200            ccc
17201            ddd
17202
17203            ggg
17204            hhh
17205
17206
17207            lll
17208            mmm
17209            NNN
17210
17211            qqq
17212            rrr
17213
17214            uuu
17215            111
17216            222
17217            333
17218
17219            666
17220            777
17221
17222            000
17223            !!!"
17224        .unindent(),
17225    );
17226
17227    cx.update_editor(|editor, window, cx| {
17228        editor.select_all(&SelectAll, window, cx);
17229        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17230    });
17231    cx.executor().run_until_parked();
17232
17233    cx.assert_state_with_diff(
17234        "
17235            «aaa
17236          - bbb
17237            ccc
17238            ddd
17239
17240            ggg
17241            hhh
17242
17243
17244            lll
17245            mmm
17246          - nnn
17247          + NNN
17248
17249            qqq
17250            rrr
17251
17252            uuu
17253            111
17254            222
17255            333
17256
17257          + 666
17258            777
17259
17260            000
17261            !!!ˇ»"
17262            .unindent(),
17263    );
17264}
17265
17266#[gpui::test]
17267async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
17268    init_test(cx, |_| {});
17269
17270    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
17271    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
17272
17273    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
17274    let multi_buffer = cx.new(|cx| {
17275        let mut multibuffer = MultiBuffer::new(ReadWrite);
17276        multibuffer.push_excerpts(
17277            buffer.clone(),
17278            [
17279                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
17280                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
17281                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
17282            ],
17283            cx,
17284        );
17285        multibuffer
17286    });
17287
17288    let editor =
17289        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17290    editor
17291        .update(cx, |editor, _window, cx| {
17292            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
17293            editor
17294                .buffer
17295                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
17296        })
17297        .unwrap();
17298
17299    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17300    cx.run_until_parked();
17301
17302    cx.update_editor(|editor, window, cx| {
17303        editor.expand_all_diff_hunks(&Default::default(), window, cx)
17304    });
17305    cx.executor().run_until_parked();
17306
17307    // When the start of a hunk coincides with the start of its excerpt,
17308    // the hunk is expanded. When the start of a a hunk is earlier than
17309    // the start of its excerpt, the hunk is not expanded.
17310    cx.assert_state_with_diff(
17311        "
17312            ˇaaa
17313          - bbb
17314          + BBB
17315
17316          - ddd
17317          - eee
17318          + DDD
17319          + EEE
17320            fff
17321
17322            iii
17323        "
17324        .unindent(),
17325    );
17326}
17327
17328#[gpui::test]
17329async fn test_edits_around_expanded_insertion_hunks(
17330    executor: BackgroundExecutor,
17331    cx: &mut TestAppContext,
17332) {
17333    init_test(cx, |_| {});
17334
17335    let mut cx = EditorTestContext::new(cx).await;
17336
17337    let diff_base = r#"
17338        use some::mod1;
17339        use some::mod2;
17340
17341        const A: u32 = 42;
17342
17343        fn main() {
17344            println!("hello");
17345
17346            println!("world");
17347        }
17348        "#
17349    .unindent();
17350    executor.run_until_parked();
17351    cx.set_state(
17352        &r#"
17353        use some::mod1;
17354        use some::mod2;
17355
17356        const A: u32 = 42;
17357        const B: u32 = 42;
17358        const C: u32 = 42;
17359        ˇ
17360
17361        fn main() {
17362            println!("hello");
17363
17364            println!("world");
17365        }
17366        "#
17367        .unindent(),
17368    );
17369
17370    cx.set_head_text(&diff_base);
17371    executor.run_until_parked();
17372
17373    cx.update_editor(|editor, window, cx| {
17374        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17375    });
17376    executor.run_until_parked();
17377
17378    cx.assert_state_with_diff(
17379        r#"
17380        use some::mod1;
17381        use some::mod2;
17382
17383        const A: u32 = 42;
17384      + const B: u32 = 42;
17385      + const C: u32 = 42;
17386      + ˇ
17387
17388        fn main() {
17389            println!("hello");
17390
17391            println!("world");
17392        }
17393      "#
17394        .unindent(),
17395    );
17396
17397    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
17398    executor.run_until_parked();
17399
17400    cx.assert_state_with_diff(
17401        r#"
17402        use some::mod1;
17403        use some::mod2;
17404
17405        const A: u32 = 42;
17406      + const B: u32 = 42;
17407      + const C: u32 = 42;
17408      + const D: u32 = 42;
17409      + ˇ
17410
17411        fn main() {
17412            println!("hello");
17413
17414            println!("world");
17415        }
17416      "#
17417        .unindent(),
17418    );
17419
17420    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
17421    executor.run_until_parked();
17422
17423    cx.assert_state_with_diff(
17424        r#"
17425        use some::mod1;
17426        use some::mod2;
17427
17428        const A: u32 = 42;
17429      + const B: u32 = 42;
17430      + const C: u32 = 42;
17431      + const D: u32 = 42;
17432      + const E: u32 = 42;
17433      + ˇ
17434
17435        fn main() {
17436            println!("hello");
17437
17438            println!("world");
17439        }
17440      "#
17441        .unindent(),
17442    );
17443
17444    cx.update_editor(|editor, window, cx| {
17445        editor.delete_line(&DeleteLine, window, cx);
17446    });
17447    executor.run_until_parked();
17448
17449    cx.assert_state_with_diff(
17450        r#"
17451        use some::mod1;
17452        use some::mod2;
17453
17454        const A: u32 = 42;
17455      + const B: u32 = 42;
17456      + const C: u32 = 42;
17457      + const D: u32 = 42;
17458      + const E: u32 = 42;
17459        ˇ
17460        fn main() {
17461            println!("hello");
17462
17463            println!("world");
17464        }
17465      "#
17466        .unindent(),
17467    );
17468
17469    cx.update_editor(|editor, window, cx| {
17470        editor.move_up(&MoveUp, window, cx);
17471        editor.delete_line(&DeleteLine, window, cx);
17472        editor.move_up(&MoveUp, window, cx);
17473        editor.delete_line(&DeleteLine, window, cx);
17474        editor.move_up(&MoveUp, window, cx);
17475        editor.delete_line(&DeleteLine, window, cx);
17476    });
17477    executor.run_until_parked();
17478    cx.assert_state_with_diff(
17479        r#"
17480        use some::mod1;
17481        use some::mod2;
17482
17483        const A: u32 = 42;
17484      + const B: u32 = 42;
17485        ˇ
17486        fn main() {
17487            println!("hello");
17488
17489            println!("world");
17490        }
17491      "#
17492        .unindent(),
17493    );
17494
17495    cx.update_editor(|editor, window, cx| {
17496        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
17497        editor.delete_line(&DeleteLine, window, cx);
17498    });
17499    executor.run_until_parked();
17500    cx.assert_state_with_diff(
17501        r#"
17502        ˇ
17503        fn main() {
17504            println!("hello");
17505
17506            println!("world");
17507        }
17508      "#
17509        .unindent(),
17510    );
17511}
17512
17513#[gpui::test]
17514async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
17515    init_test(cx, |_| {});
17516
17517    let mut cx = EditorTestContext::new(cx).await;
17518    cx.set_head_text(indoc! { "
17519        one
17520        two
17521        three
17522        four
17523        five
17524        "
17525    });
17526    cx.set_state(indoc! { "
17527        one
17528        ˇthree
17529        five
17530    "});
17531    cx.run_until_parked();
17532    cx.update_editor(|editor, window, cx| {
17533        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17534    });
17535    cx.assert_state_with_diff(
17536        indoc! { "
17537        one
17538      - two
17539        ˇthree
17540      - four
17541        five
17542    "}
17543        .to_string(),
17544    );
17545    cx.update_editor(|editor, window, cx| {
17546        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17547    });
17548
17549    cx.assert_state_with_diff(
17550        indoc! { "
17551        one
17552        ˇthree
17553        five
17554    "}
17555        .to_string(),
17556    );
17557
17558    cx.set_state(indoc! { "
17559        one
17560        ˇTWO
17561        three
17562        four
17563        five
17564    "});
17565    cx.run_until_parked();
17566    cx.update_editor(|editor, window, cx| {
17567        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17568    });
17569
17570    cx.assert_state_with_diff(
17571        indoc! { "
17572            one
17573          - two
17574          + ˇTWO
17575            three
17576            four
17577            five
17578        "}
17579        .to_string(),
17580    );
17581    cx.update_editor(|editor, window, cx| {
17582        editor.move_up(&Default::default(), window, cx);
17583        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
17584    });
17585    cx.assert_state_with_diff(
17586        indoc! { "
17587            one
17588            ˇTWO
17589            three
17590            four
17591            five
17592        "}
17593        .to_string(),
17594    );
17595}
17596
17597#[gpui::test]
17598async fn test_edits_around_expanded_deletion_hunks(
17599    executor: BackgroundExecutor,
17600    cx: &mut TestAppContext,
17601) {
17602    init_test(cx, |_| {});
17603
17604    let mut cx = EditorTestContext::new(cx).await;
17605
17606    let diff_base = r#"
17607        use some::mod1;
17608        use some::mod2;
17609
17610        const A: u32 = 42;
17611        const B: u32 = 42;
17612        const C: u32 = 42;
17613
17614
17615        fn main() {
17616            println!("hello");
17617
17618            println!("world");
17619        }
17620    "#
17621    .unindent();
17622    executor.run_until_parked();
17623    cx.set_state(
17624        &r#"
17625        use some::mod1;
17626        use some::mod2;
17627
17628        ˇconst B: u32 = 42;
17629        const C: u32 = 42;
17630
17631
17632        fn main() {
17633            println!("hello");
17634
17635            println!("world");
17636        }
17637        "#
17638        .unindent(),
17639    );
17640
17641    cx.set_head_text(&diff_base);
17642    executor.run_until_parked();
17643
17644    cx.update_editor(|editor, window, cx| {
17645        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17646    });
17647    executor.run_until_parked();
17648
17649    cx.assert_state_with_diff(
17650        r#"
17651        use some::mod1;
17652        use some::mod2;
17653
17654      - const A: u32 = 42;
17655        ˇconst B: u32 = 42;
17656        const C: u32 = 42;
17657
17658
17659        fn main() {
17660            println!("hello");
17661
17662            println!("world");
17663        }
17664      "#
17665        .unindent(),
17666    );
17667
17668    cx.update_editor(|editor, window, cx| {
17669        editor.delete_line(&DeleteLine, window, cx);
17670    });
17671    executor.run_until_parked();
17672    cx.assert_state_with_diff(
17673        r#"
17674        use some::mod1;
17675        use some::mod2;
17676
17677      - const A: u32 = 42;
17678      - const B: u32 = 42;
17679        ˇconst C: u32 = 42;
17680
17681
17682        fn main() {
17683            println!("hello");
17684
17685            println!("world");
17686        }
17687      "#
17688        .unindent(),
17689    );
17690
17691    cx.update_editor(|editor, window, cx| {
17692        editor.delete_line(&DeleteLine, window, cx);
17693    });
17694    executor.run_until_parked();
17695    cx.assert_state_with_diff(
17696        r#"
17697        use some::mod1;
17698        use some::mod2;
17699
17700      - const A: u32 = 42;
17701      - const B: u32 = 42;
17702      - const C: u32 = 42;
17703        ˇ
17704
17705        fn main() {
17706            println!("hello");
17707
17708            println!("world");
17709        }
17710      "#
17711        .unindent(),
17712    );
17713
17714    cx.update_editor(|editor, window, cx| {
17715        editor.handle_input("replacement", window, cx);
17716    });
17717    executor.run_until_parked();
17718    cx.assert_state_with_diff(
17719        r#"
17720        use some::mod1;
17721        use some::mod2;
17722
17723      - const A: u32 = 42;
17724      - const B: u32 = 42;
17725      - const C: u32 = 42;
17726      -
17727      + replacementˇ
17728
17729        fn main() {
17730            println!("hello");
17731
17732            println!("world");
17733        }
17734      "#
17735        .unindent(),
17736    );
17737}
17738
17739#[gpui::test]
17740async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17741    init_test(cx, |_| {});
17742
17743    let mut cx = EditorTestContext::new(cx).await;
17744
17745    let base_text = r#"
17746        one
17747        two
17748        three
17749        four
17750        five
17751    "#
17752    .unindent();
17753    executor.run_until_parked();
17754    cx.set_state(
17755        &r#"
17756        one
17757        two
17758        fˇour
17759        five
17760        "#
17761        .unindent(),
17762    );
17763
17764    cx.set_head_text(&base_text);
17765    executor.run_until_parked();
17766
17767    cx.update_editor(|editor, window, cx| {
17768        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17769    });
17770    executor.run_until_parked();
17771
17772    cx.assert_state_with_diff(
17773        r#"
17774          one
17775          two
17776        - three
17777          fˇour
17778          five
17779        "#
17780        .unindent(),
17781    );
17782
17783    cx.update_editor(|editor, window, cx| {
17784        editor.backspace(&Backspace, window, cx);
17785        editor.backspace(&Backspace, window, cx);
17786    });
17787    executor.run_until_parked();
17788    cx.assert_state_with_diff(
17789        r#"
17790          one
17791          two
17792        - threeˇ
17793        - four
17794        + our
17795          five
17796        "#
17797        .unindent(),
17798    );
17799}
17800
17801#[gpui::test]
17802async fn test_edit_after_expanded_modification_hunk(
17803    executor: BackgroundExecutor,
17804    cx: &mut TestAppContext,
17805) {
17806    init_test(cx, |_| {});
17807
17808    let mut cx = EditorTestContext::new(cx).await;
17809
17810    let diff_base = r#"
17811        use some::mod1;
17812        use some::mod2;
17813
17814        const A: u32 = 42;
17815        const B: u32 = 42;
17816        const C: u32 = 42;
17817        const D: u32 = 42;
17818
17819
17820        fn main() {
17821            println!("hello");
17822
17823            println!("world");
17824        }"#
17825    .unindent();
17826
17827    cx.set_state(
17828        &r#"
17829        use some::mod1;
17830        use some::mod2;
17831
17832        const A: u32 = 42;
17833        const B: u32 = 42;
17834        const C: u32 = 43ˇ
17835        const D: u32 = 42;
17836
17837
17838        fn main() {
17839            println!("hello");
17840
17841            println!("world");
17842        }"#
17843        .unindent(),
17844    );
17845
17846    cx.set_head_text(&diff_base);
17847    executor.run_until_parked();
17848    cx.update_editor(|editor, window, cx| {
17849        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17850    });
17851    executor.run_until_parked();
17852
17853    cx.assert_state_with_diff(
17854        r#"
17855        use some::mod1;
17856        use some::mod2;
17857
17858        const A: u32 = 42;
17859        const B: u32 = 42;
17860      - const C: u32 = 42;
17861      + const C: u32 = 43ˇ
17862        const D: u32 = 42;
17863
17864
17865        fn main() {
17866            println!("hello");
17867
17868            println!("world");
17869        }"#
17870        .unindent(),
17871    );
17872
17873    cx.update_editor(|editor, window, cx| {
17874        editor.handle_input("\nnew_line\n", window, cx);
17875    });
17876    executor.run_until_parked();
17877
17878    cx.assert_state_with_diff(
17879        r#"
17880        use some::mod1;
17881        use some::mod2;
17882
17883        const A: u32 = 42;
17884        const B: u32 = 42;
17885      - const C: u32 = 42;
17886      + const C: u32 = 43
17887      + new_line
17888      + ˇ
17889        const D: u32 = 42;
17890
17891
17892        fn main() {
17893            println!("hello");
17894
17895            println!("world");
17896        }"#
17897        .unindent(),
17898    );
17899}
17900
17901#[gpui::test]
17902async fn test_stage_and_unstage_added_file_hunk(
17903    executor: BackgroundExecutor,
17904    cx: &mut TestAppContext,
17905) {
17906    init_test(cx, |_| {});
17907
17908    let mut cx = EditorTestContext::new(cx).await;
17909    cx.update_editor(|editor, _, cx| {
17910        editor.set_expand_all_diff_hunks(cx);
17911    });
17912
17913    let working_copy = r#"
17914            ˇfn main() {
17915                println!("hello, world!");
17916            }
17917        "#
17918    .unindent();
17919
17920    cx.set_state(&working_copy);
17921    executor.run_until_parked();
17922
17923    cx.assert_state_with_diff(
17924        r#"
17925            + ˇfn main() {
17926            +     println!("hello, world!");
17927            + }
17928        "#
17929        .unindent(),
17930    );
17931    cx.assert_index_text(None);
17932
17933    cx.update_editor(|editor, window, cx| {
17934        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17935    });
17936    executor.run_until_parked();
17937    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
17938    cx.assert_state_with_diff(
17939        r#"
17940            + ˇfn main() {
17941            +     println!("hello, world!");
17942            + }
17943        "#
17944        .unindent(),
17945    );
17946
17947    cx.update_editor(|editor, window, cx| {
17948        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
17949    });
17950    executor.run_until_parked();
17951    cx.assert_index_text(None);
17952}
17953
17954async fn setup_indent_guides_editor(
17955    text: &str,
17956    cx: &mut TestAppContext,
17957) -> (BufferId, EditorTestContext) {
17958    init_test(cx, |_| {});
17959
17960    let mut cx = EditorTestContext::new(cx).await;
17961
17962    let buffer_id = cx.update_editor(|editor, window, cx| {
17963        editor.set_text(text, window, cx);
17964        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
17965
17966        buffer_ids[0]
17967    });
17968
17969    (buffer_id, cx)
17970}
17971
17972fn assert_indent_guides(
17973    range: Range<u32>,
17974    expected: Vec<IndentGuide>,
17975    active_indices: Option<Vec<usize>>,
17976    cx: &mut EditorTestContext,
17977) {
17978    let indent_guides = cx.update_editor(|editor, window, cx| {
17979        let snapshot = editor.snapshot(window, cx).display_snapshot;
17980        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
17981            editor,
17982            MultiBufferRow(range.start)..MultiBufferRow(range.end),
17983            true,
17984            &snapshot,
17985            cx,
17986        );
17987
17988        indent_guides.sort_by(|a, b| {
17989            a.depth.cmp(&b.depth).then(
17990                a.start_row
17991                    .cmp(&b.start_row)
17992                    .then(a.end_row.cmp(&b.end_row)),
17993            )
17994        });
17995        indent_guides
17996    });
17997
17998    if let Some(expected) = active_indices {
17999        let active_indices = cx.update_editor(|editor, window, cx| {
18000            let snapshot = editor.snapshot(window, cx).display_snapshot;
18001            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18002        });
18003
18004        assert_eq!(
18005            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18006            expected,
18007            "Active indent guide indices do not match"
18008        );
18009    }
18010
18011    assert_eq!(indent_guides, expected, "Indent guides do not match");
18012}
18013
18014fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18015    IndentGuide {
18016        buffer_id,
18017        start_row: MultiBufferRow(start_row),
18018        end_row: MultiBufferRow(end_row),
18019        depth,
18020        tab_size: 4,
18021        settings: IndentGuideSettings {
18022            enabled: true,
18023            line_width: 1,
18024            active_line_width: 1,
18025            ..Default::default()
18026        },
18027    }
18028}
18029
18030#[gpui::test]
18031async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18032    let (buffer_id, mut cx) = setup_indent_guides_editor(
18033        &"
18034        fn main() {
18035            let a = 1;
18036        }"
18037        .unindent(),
18038        cx,
18039    )
18040    .await;
18041
18042    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18043}
18044
18045#[gpui::test]
18046async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18047    let (buffer_id, mut cx) = setup_indent_guides_editor(
18048        &"
18049        fn main() {
18050            let a = 1;
18051            let b = 2;
18052        }"
18053        .unindent(),
18054        cx,
18055    )
18056    .await;
18057
18058    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18059}
18060
18061#[gpui::test]
18062async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18063    let (buffer_id, mut cx) = setup_indent_guides_editor(
18064        &"
18065        fn main() {
18066            let a = 1;
18067            if a == 3 {
18068                let b = 2;
18069            } else {
18070                let c = 3;
18071            }
18072        }"
18073        .unindent(),
18074        cx,
18075    )
18076    .await;
18077
18078    assert_indent_guides(
18079        0..8,
18080        vec![
18081            indent_guide(buffer_id, 1, 6, 0),
18082            indent_guide(buffer_id, 3, 3, 1),
18083            indent_guide(buffer_id, 5, 5, 1),
18084        ],
18085        None,
18086        &mut cx,
18087    );
18088}
18089
18090#[gpui::test]
18091async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18092    let (buffer_id, mut cx) = setup_indent_guides_editor(
18093        &"
18094        fn main() {
18095            let a = 1;
18096                let b = 2;
18097            let c = 3;
18098        }"
18099        .unindent(),
18100        cx,
18101    )
18102    .await;
18103
18104    assert_indent_guides(
18105        0..5,
18106        vec![
18107            indent_guide(buffer_id, 1, 3, 0),
18108            indent_guide(buffer_id, 2, 2, 1),
18109        ],
18110        None,
18111        &mut cx,
18112    );
18113}
18114
18115#[gpui::test]
18116async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18117    let (buffer_id, mut cx) = setup_indent_guides_editor(
18118        &"
18119        fn main() {
18120            let a = 1;
18121
18122            let c = 3;
18123        }"
18124        .unindent(),
18125        cx,
18126    )
18127    .await;
18128
18129    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18130}
18131
18132#[gpui::test]
18133async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18134    let (buffer_id, mut cx) = setup_indent_guides_editor(
18135        &"
18136        fn main() {
18137            let a = 1;
18138
18139            let c = 3;
18140
18141            if a == 3 {
18142                let b = 2;
18143            } else {
18144                let c = 3;
18145            }
18146        }"
18147        .unindent(),
18148        cx,
18149    )
18150    .await;
18151
18152    assert_indent_guides(
18153        0..11,
18154        vec![
18155            indent_guide(buffer_id, 1, 9, 0),
18156            indent_guide(buffer_id, 6, 6, 1),
18157            indent_guide(buffer_id, 8, 8, 1),
18158        ],
18159        None,
18160        &mut cx,
18161    );
18162}
18163
18164#[gpui::test]
18165async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18166    let (buffer_id, mut cx) = setup_indent_guides_editor(
18167        &"
18168        fn main() {
18169            let a = 1;
18170
18171            let c = 3;
18172
18173            if a == 3 {
18174                let b = 2;
18175            } else {
18176                let c = 3;
18177            }
18178        }"
18179        .unindent(),
18180        cx,
18181    )
18182    .await;
18183
18184    assert_indent_guides(
18185        1..11,
18186        vec![
18187            indent_guide(buffer_id, 1, 9, 0),
18188            indent_guide(buffer_id, 6, 6, 1),
18189            indent_guide(buffer_id, 8, 8, 1),
18190        ],
18191        None,
18192        &mut cx,
18193    );
18194}
18195
18196#[gpui::test]
18197async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18198    let (buffer_id, mut cx) = setup_indent_guides_editor(
18199        &"
18200        fn main() {
18201            let a = 1;
18202
18203            let c = 3;
18204
18205            if a == 3 {
18206                let b = 2;
18207            } else {
18208                let c = 3;
18209            }
18210        }"
18211        .unindent(),
18212        cx,
18213    )
18214    .await;
18215
18216    assert_indent_guides(
18217        1..10,
18218        vec![
18219            indent_guide(buffer_id, 1, 9, 0),
18220            indent_guide(buffer_id, 6, 6, 1),
18221            indent_guide(buffer_id, 8, 8, 1),
18222        ],
18223        None,
18224        &mut cx,
18225    );
18226}
18227
18228#[gpui::test]
18229async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
18230    let (buffer_id, mut cx) = setup_indent_guides_editor(
18231        &"
18232        fn main() {
18233            if a {
18234                b(
18235                    c,
18236                    d,
18237                )
18238            } else {
18239                e(
18240                    f
18241                )
18242            }
18243        }"
18244        .unindent(),
18245        cx,
18246    )
18247    .await;
18248
18249    assert_indent_guides(
18250        0..11,
18251        vec![
18252            indent_guide(buffer_id, 1, 10, 0),
18253            indent_guide(buffer_id, 2, 5, 1),
18254            indent_guide(buffer_id, 7, 9, 1),
18255            indent_guide(buffer_id, 3, 4, 2),
18256            indent_guide(buffer_id, 8, 8, 2),
18257        ],
18258        None,
18259        &mut cx,
18260    );
18261
18262    cx.update_editor(|editor, window, cx| {
18263        editor.fold_at(MultiBufferRow(2), window, cx);
18264        assert_eq!(
18265            editor.display_text(cx),
18266            "
18267            fn main() {
18268                if a {
18269                    b(⋯
18270                    )
18271                } else {
18272                    e(
18273                        f
18274                    )
18275                }
18276            }"
18277            .unindent()
18278        );
18279    });
18280
18281    assert_indent_guides(
18282        0..11,
18283        vec![
18284            indent_guide(buffer_id, 1, 10, 0),
18285            indent_guide(buffer_id, 2, 5, 1),
18286            indent_guide(buffer_id, 7, 9, 1),
18287            indent_guide(buffer_id, 8, 8, 2),
18288        ],
18289        None,
18290        &mut cx,
18291    );
18292}
18293
18294#[gpui::test]
18295async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
18296    let (buffer_id, mut cx) = setup_indent_guides_editor(
18297        &"
18298        block1
18299            block2
18300                block3
18301                    block4
18302            block2
18303        block1
18304        block1"
18305            .unindent(),
18306        cx,
18307    )
18308    .await;
18309
18310    assert_indent_guides(
18311        1..10,
18312        vec![
18313            indent_guide(buffer_id, 1, 4, 0),
18314            indent_guide(buffer_id, 2, 3, 1),
18315            indent_guide(buffer_id, 3, 3, 2),
18316        ],
18317        None,
18318        &mut cx,
18319    );
18320}
18321
18322#[gpui::test]
18323async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
18324    let (buffer_id, mut cx) = setup_indent_guides_editor(
18325        &"
18326        block1
18327            block2
18328                block3
18329
18330        block1
18331        block1"
18332            .unindent(),
18333        cx,
18334    )
18335    .await;
18336
18337    assert_indent_guides(
18338        0..6,
18339        vec![
18340            indent_guide(buffer_id, 1, 2, 0),
18341            indent_guide(buffer_id, 2, 2, 1),
18342        ],
18343        None,
18344        &mut cx,
18345    );
18346}
18347
18348#[gpui::test]
18349async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
18350    let (buffer_id, mut cx) = setup_indent_guides_editor(
18351        &"
18352        function component() {
18353        \treturn (
18354        \t\t\t
18355        \t\t<div>
18356        \t\t\t<abc></abc>
18357        \t\t</div>
18358        \t)
18359        }"
18360        .unindent(),
18361        cx,
18362    )
18363    .await;
18364
18365    assert_indent_guides(
18366        0..8,
18367        vec![
18368            indent_guide(buffer_id, 1, 6, 0),
18369            indent_guide(buffer_id, 2, 5, 1),
18370            indent_guide(buffer_id, 4, 4, 2),
18371        ],
18372        None,
18373        &mut cx,
18374    );
18375}
18376
18377#[gpui::test]
18378async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
18379    let (buffer_id, mut cx) = setup_indent_guides_editor(
18380        &"
18381        function component() {
18382        \treturn (
18383        \t
18384        \t\t<div>
18385        \t\t\t<abc></abc>
18386        \t\t</div>
18387        \t)
18388        }"
18389        .unindent(),
18390        cx,
18391    )
18392    .await;
18393
18394    assert_indent_guides(
18395        0..8,
18396        vec![
18397            indent_guide(buffer_id, 1, 6, 0),
18398            indent_guide(buffer_id, 2, 5, 1),
18399            indent_guide(buffer_id, 4, 4, 2),
18400        ],
18401        None,
18402        &mut cx,
18403    );
18404}
18405
18406#[gpui::test]
18407async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
18408    let (buffer_id, mut cx) = setup_indent_guides_editor(
18409        &"
18410        block1
18411
18412
18413
18414            block2
18415        "
18416        .unindent(),
18417        cx,
18418    )
18419    .await;
18420
18421    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18422}
18423
18424#[gpui::test]
18425async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
18426    let (buffer_id, mut cx) = setup_indent_guides_editor(
18427        &"
18428        def a:
18429        \tb = 3
18430        \tif True:
18431        \t\tc = 4
18432        \t\td = 5
18433        \tprint(b)
18434        "
18435        .unindent(),
18436        cx,
18437    )
18438    .await;
18439
18440    assert_indent_guides(
18441        0..6,
18442        vec![
18443            indent_guide(buffer_id, 1, 5, 0),
18444            indent_guide(buffer_id, 3, 4, 1),
18445        ],
18446        None,
18447        &mut cx,
18448    );
18449}
18450
18451#[gpui::test]
18452async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
18453    let (buffer_id, mut cx) = setup_indent_guides_editor(
18454        &"
18455    fn main() {
18456        let a = 1;
18457    }"
18458        .unindent(),
18459        cx,
18460    )
18461    .await;
18462
18463    cx.update_editor(|editor, window, cx| {
18464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18465            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18466        });
18467    });
18468
18469    assert_indent_guides(
18470        0..3,
18471        vec![indent_guide(buffer_id, 1, 1, 0)],
18472        Some(vec![0]),
18473        &mut cx,
18474    );
18475}
18476
18477#[gpui::test]
18478async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
18479    let (buffer_id, mut cx) = setup_indent_guides_editor(
18480        &"
18481    fn main() {
18482        if 1 == 2 {
18483            let a = 1;
18484        }
18485    }"
18486        .unindent(),
18487        cx,
18488    )
18489    .await;
18490
18491    cx.update_editor(|editor, window, cx| {
18492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18493            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18494        });
18495    });
18496
18497    assert_indent_guides(
18498        0..4,
18499        vec![
18500            indent_guide(buffer_id, 1, 3, 0),
18501            indent_guide(buffer_id, 2, 2, 1),
18502        ],
18503        Some(vec![1]),
18504        &mut cx,
18505    );
18506
18507    cx.update_editor(|editor, window, cx| {
18508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18509            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18510        });
18511    });
18512
18513    assert_indent_guides(
18514        0..4,
18515        vec![
18516            indent_guide(buffer_id, 1, 3, 0),
18517            indent_guide(buffer_id, 2, 2, 1),
18518        ],
18519        Some(vec![1]),
18520        &mut cx,
18521    );
18522
18523    cx.update_editor(|editor, window, cx| {
18524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18525            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
18526        });
18527    });
18528
18529    assert_indent_guides(
18530        0..4,
18531        vec![
18532            indent_guide(buffer_id, 1, 3, 0),
18533            indent_guide(buffer_id, 2, 2, 1),
18534        ],
18535        Some(vec![0]),
18536        &mut cx,
18537    );
18538}
18539
18540#[gpui::test]
18541async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
18542    let (buffer_id, mut cx) = setup_indent_guides_editor(
18543        &"
18544    fn main() {
18545        let a = 1;
18546
18547        let b = 2;
18548    }"
18549        .unindent(),
18550        cx,
18551    )
18552    .await;
18553
18554    cx.update_editor(|editor, window, cx| {
18555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18556            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
18557        });
18558    });
18559
18560    assert_indent_guides(
18561        0..5,
18562        vec![indent_guide(buffer_id, 1, 3, 0)],
18563        Some(vec![0]),
18564        &mut cx,
18565    );
18566}
18567
18568#[gpui::test]
18569async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
18570    let (buffer_id, mut cx) = setup_indent_guides_editor(
18571        &"
18572    def m:
18573        a = 1
18574        pass"
18575            .unindent(),
18576        cx,
18577    )
18578    .await;
18579
18580    cx.update_editor(|editor, window, cx| {
18581        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18582            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
18583        });
18584    });
18585
18586    assert_indent_guides(
18587        0..3,
18588        vec![indent_guide(buffer_id, 1, 2, 0)],
18589        Some(vec![0]),
18590        &mut cx,
18591    );
18592}
18593
18594#[gpui::test]
18595async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
18596    init_test(cx, |_| {});
18597    let mut cx = EditorTestContext::new(cx).await;
18598    let text = indoc! {
18599        "
18600        impl A {
18601            fn b() {
18602                0;
18603                3;
18604                5;
18605                6;
18606                7;
18607            }
18608        }
18609        "
18610    };
18611    let base_text = indoc! {
18612        "
18613        impl A {
18614            fn b() {
18615                0;
18616                1;
18617                2;
18618                3;
18619                4;
18620            }
18621            fn c() {
18622                5;
18623                6;
18624                7;
18625            }
18626        }
18627        "
18628    };
18629
18630    cx.update_editor(|editor, window, cx| {
18631        editor.set_text(text, window, cx);
18632
18633        editor.buffer().update(cx, |multibuffer, cx| {
18634            let buffer = multibuffer.as_singleton().unwrap();
18635            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
18636
18637            multibuffer.set_all_diff_hunks_expanded(cx);
18638            multibuffer.add_diff(diff, cx);
18639
18640            buffer.read(cx).remote_id()
18641        })
18642    });
18643    cx.run_until_parked();
18644
18645    cx.assert_state_with_diff(
18646        indoc! { "
18647          impl A {
18648              fn b() {
18649                  0;
18650        -         1;
18651        -         2;
18652                  3;
18653        -         4;
18654        -     }
18655        -     fn c() {
18656                  5;
18657                  6;
18658                  7;
18659              }
18660          }
18661          ˇ"
18662        }
18663        .to_string(),
18664    );
18665
18666    let mut actual_guides = cx.update_editor(|editor, window, cx| {
18667        editor
18668            .snapshot(window, cx)
18669            .buffer_snapshot
18670            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
18671            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
18672            .collect::<Vec<_>>()
18673    });
18674    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
18675    assert_eq!(
18676        actual_guides,
18677        vec![
18678            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
18679            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
18680            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
18681        ]
18682    );
18683}
18684
18685#[gpui::test]
18686async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18687    init_test(cx, |_| {});
18688    let mut cx = EditorTestContext::new(cx).await;
18689
18690    let diff_base = r#"
18691        a
18692        b
18693        c
18694        "#
18695    .unindent();
18696
18697    cx.set_state(
18698        &r#"
18699        ˇA
18700        b
18701        C
18702        "#
18703        .unindent(),
18704    );
18705    cx.set_head_text(&diff_base);
18706    cx.update_editor(|editor, window, cx| {
18707        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18708    });
18709    executor.run_until_parked();
18710
18711    let both_hunks_expanded = r#"
18712        - a
18713        + ˇA
18714          b
18715        - c
18716        + C
18717        "#
18718    .unindent();
18719
18720    cx.assert_state_with_diff(both_hunks_expanded.clone());
18721
18722    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18723        let snapshot = editor.snapshot(window, cx);
18724        let hunks = editor
18725            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18726            .collect::<Vec<_>>();
18727        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18728        let buffer_id = hunks[0].buffer_id;
18729        hunks
18730            .into_iter()
18731            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18732            .collect::<Vec<_>>()
18733    });
18734    assert_eq!(hunk_ranges.len(), 2);
18735
18736    cx.update_editor(|editor, _, cx| {
18737        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18738    });
18739    executor.run_until_parked();
18740
18741    let second_hunk_expanded = r#"
18742          ˇA
18743          b
18744        - c
18745        + C
18746        "#
18747    .unindent();
18748
18749    cx.assert_state_with_diff(second_hunk_expanded);
18750
18751    cx.update_editor(|editor, _, cx| {
18752        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18753    });
18754    executor.run_until_parked();
18755
18756    cx.assert_state_with_diff(both_hunks_expanded.clone());
18757
18758    cx.update_editor(|editor, _, cx| {
18759        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18760    });
18761    executor.run_until_parked();
18762
18763    let first_hunk_expanded = r#"
18764        - a
18765        + ˇA
18766          b
18767          C
18768        "#
18769    .unindent();
18770
18771    cx.assert_state_with_diff(first_hunk_expanded);
18772
18773    cx.update_editor(|editor, _, cx| {
18774        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18775    });
18776    executor.run_until_parked();
18777
18778    cx.assert_state_with_diff(both_hunks_expanded);
18779
18780    cx.set_state(
18781        &r#"
18782        ˇA
18783        b
18784        "#
18785        .unindent(),
18786    );
18787    cx.run_until_parked();
18788
18789    // TODO this cursor position seems bad
18790    cx.assert_state_with_diff(
18791        r#"
18792        - ˇa
18793        + A
18794          b
18795        "#
18796        .unindent(),
18797    );
18798
18799    cx.update_editor(|editor, window, cx| {
18800        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18801    });
18802
18803    cx.assert_state_with_diff(
18804        r#"
18805            - ˇa
18806            + A
18807              b
18808            - c
18809            "#
18810        .unindent(),
18811    );
18812
18813    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18814        let snapshot = editor.snapshot(window, cx);
18815        let hunks = editor
18816            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18817            .collect::<Vec<_>>();
18818        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18819        let buffer_id = hunks[0].buffer_id;
18820        hunks
18821            .into_iter()
18822            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18823            .collect::<Vec<_>>()
18824    });
18825    assert_eq!(hunk_ranges.len(), 2);
18826
18827    cx.update_editor(|editor, _, cx| {
18828        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
18829    });
18830    executor.run_until_parked();
18831
18832    cx.assert_state_with_diff(
18833        r#"
18834        - ˇa
18835        + A
18836          b
18837        "#
18838        .unindent(),
18839    );
18840}
18841
18842#[gpui::test]
18843async fn test_toggle_deletion_hunk_at_start_of_file(
18844    executor: BackgroundExecutor,
18845    cx: &mut TestAppContext,
18846) {
18847    init_test(cx, |_| {});
18848    let mut cx = EditorTestContext::new(cx).await;
18849
18850    let diff_base = r#"
18851        a
18852        b
18853        c
18854        "#
18855    .unindent();
18856
18857    cx.set_state(
18858        &r#"
18859        ˇb
18860        c
18861        "#
18862        .unindent(),
18863    );
18864    cx.set_head_text(&diff_base);
18865    cx.update_editor(|editor, window, cx| {
18866        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18867    });
18868    executor.run_until_parked();
18869
18870    let hunk_expanded = r#"
18871        - a
18872          ˇb
18873          c
18874        "#
18875    .unindent();
18876
18877    cx.assert_state_with_diff(hunk_expanded.clone());
18878
18879    let hunk_ranges = cx.update_editor(|editor, window, cx| {
18880        let snapshot = editor.snapshot(window, cx);
18881        let hunks = editor
18882            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
18883            .collect::<Vec<_>>();
18884        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
18885        let buffer_id = hunks[0].buffer_id;
18886        hunks
18887            .into_iter()
18888            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
18889            .collect::<Vec<_>>()
18890    });
18891    assert_eq!(hunk_ranges.len(), 1);
18892
18893    cx.update_editor(|editor, _, cx| {
18894        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18895    });
18896    executor.run_until_parked();
18897
18898    let hunk_collapsed = r#"
18899          ˇb
18900          c
18901        "#
18902    .unindent();
18903
18904    cx.assert_state_with_diff(hunk_collapsed);
18905
18906    cx.update_editor(|editor, _, cx| {
18907        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
18908    });
18909    executor.run_until_parked();
18910
18911    cx.assert_state_with_diff(hunk_expanded.clone());
18912}
18913
18914#[gpui::test]
18915async fn test_display_diff_hunks(cx: &mut TestAppContext) {
18916    init_test(cx, |_| {});
18917
18918    let fs = FakeFs::new(cx.executor());
18919    fs.insert_tree(
18920        path!("/test"),
18921        json!({
18922            ".git": {},
18923            "file-1": "ONE\n",
18924            "file-2": "TWO\n",
18925            "file-3": "THREE\n",
18926        }),
18927    )
18928    .await;
18929
18930    fs.set_head_for_repo(
18931        path!("/test/.git").as_ref(),
18932        &[
18933            ("file-1".into(), "one\n".into()),
18934            ("file-2".into(), "two\n".into()),
18935            ("file-3".into(), "three\n".into()),
18936        ],
18937        "deadbeef",
18938    );
18939
18940    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
18941    let mut buffers = vec![];
18942    for i in 1..=3 {
18943        let buffer = project
18944            .update(cx, |project, cx| {
18945                let path = format!(path!("/test/file-{}"), i);
18946                project.open_local_buffer(path, cx)
18947            })
18948            .await
18949            .unwrap();
18950        buffers.push(buffer);
18951    }
18952
18953    let multibuffer = cx.new(|cx| {
18954        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
18955        multibuffer.set_all_diff_hunks_expanded(cx);
18956        for buffer in &buffers {
18957            let snapshot = buffer.read(cx).snapshot();
18958            multibuffer.set_excerpts_for_path(
18959                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
18960                buffer.clone(),
18961                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
18962                DEFAULT_MULTIBUFFER_CONTEXT,
18963                cx,
18964            );
18965        }
18966        multibuffer
18967    });
18968
18969    let editor = cx.add_window(|window, cx| {
18970        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
18971    });
18972    cx.run_until_parked();
18973
18974    let snapshot = editor
18975        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
18976        .unwrap();
18977    let hunks = snapshot
18978        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
18979        .map(|hunk| match hunk {
18980            DisplayDiffHunk::Unfolded {
18981                display_row_range, ..
18982            } => display_row_range,
18983            DisplayDiffHunk::Folded { .. } => unreachable!(),
18984        })
18985        .collect::<Vec<_>>();
18986    assert_eq!(
18987        hunks,
18988        [
18989            DisplayRow(2)..DisplayRow(4),
18990            DisplayRow(7)..DisplayRow(9),
18991            DisplayRow(12)..DisplayRow(14),
18992        ]
18993    );
18994}
18995
18996#[gpui::test]
18997async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
18998    init_test(cx, |_| {});
18999
19000    let mut cx = EditorTestContext::new(cx).await;
19001    cx.set_head_text(indoc! { "
19002        one
19003        two
19004        three
19005        four
19006        five
19007        "
19008    });
19009    cx.set_index_text(indoc! { "
19010        one
19011        two
19012        three
19013        four
19014        five
19015        "
19016    });
19017    cx.set_state(indoc! {"
19018        one
19019        TWO
19020        ˇTHREE
19021        FOUR
19022        five
19023    "});
19024    cx.run_until_parked();
19025    cx.update_editor(|editor, window, cx| {
19026        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19027    });
19028    cx.run_until_parked();
19029    cx.assert_index_text(Some(indoc! {"
19030        one
19031        TWO
19032        THREE
19033        FOUR
19034        five
19035    "}));
19036    cx.set_state(indoc! { "
19037        one
19038        TWO
19039        ˇTHREE-HUNDRED
19040        FOUR
19041        five
19042    "});
19043    cx.run_until_parked();
19044    cx.update_editor(|editor, window, cx| {
19045        let snapshot = editor.snapshot(window, cx);
19046        let hunks = editor
19047            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19048            .collect::<Vec<_>>();
19049        assert_eq!(hunks.len(), 1);
19050        assert_eq!(
19051            hunks[0].status(),
19052            DiffHunkStatus {
19053                kind: DiffHunkStatusKind::Modified,
19054                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19055            }
19056        );
19057
19058        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19059    });
19060    cx.run_until_parked();
19061    cx.assert_index_text(Some(indoc! {"
19062        one
19063        TWO
19064        THREE-HUNDRED
19065        FOUR
19066        five
19067    "}));
19068}
19069
19070#[gpui::test]
19071fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19072    init_test(cx, |_| {});
19073
19074    let editor = cx.add_window(|window, cx| {
19075        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19076        build_editor(buffer, window, cx)
19077    });
19078
19079    let render_args = Arc::new(Mutex::new(None));
19080    let snapshot = editor
19081        .update(cx, |editor, window, cx| {
19082            let snapshot = editor.buffer().read(cx).snapshot(cx);
19083            let range =
19084                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19085
19086            struct RenderArgs {
19087                row: MultiBufferRow,
19088                folded: bool,
19089                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19090            }
19091
19092            let crease = Crease::inline(
19093                range,
19094                FoldPlaceholder::test(),
19095                {
19096                    let toggle_callback = render_args.clone();
19097                    move |row, folded, callback, _window, _cx| {
19098                        *toggle_callback.lock() = Some(RenderArgs {
19099                            row,
19100                            folded,
19101                            callback,
19102                        });
19103                        div()
19104                    }
19105                },
19106                |_row, _folded, _window, _cx| div(),
19107            );
19108
19109            editor.insert_creases(Some(crease), cx);
19110            let snapshot = editor.snapshot(window, cx);
19111            let _div = snapshot.render_crease_toggle(
19112                MultiBufferRow(1),
19113                false,
19114                cx.entity().clone(),
19115                window,
19116                cx,
19117            );
19118            snapshot
19119        })
19120        .unwrap();
19121
19122    let render_args = render_args.lock().take().unwrap();
19123    assert_eq!(render_args.row, MultiBufferRow(1));
19124    assert!(!render_args.folded);
19125    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19126
19127    cx.update_window(*editor, |_, window, cx| {
19128        (render_args.callback)(true, window, cx)
19129    })
19130    .unwrap();
19131    let snapshot = editor
19132        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19133        .unwrap();
19134    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19135
19136    cx.update_window(*editor, |_, window, cx| {
19137        (render_args.callback)(false, window, cx)
19138    })
19139    .unwrap();
19140    let snapshot = editor
19141        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19142        .unwrap();
19143    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19144}
19145
19146#[gpui::test]
19147async fn test_input_text(cx: &mut TestAppContext) {
19148    init_test(cx, |_| {});
19149    let mut cx = EditorTestContext::new(cx).await;
19150
19151    cx.set_state(
19152        &r#"ˇone
19153        two
19154
19155        three
19156        fourˇ
19157        five
19158
19159        siˇx"#
19160            .unindent(),
19161    );
19162
19163    cx.dispatch_action(HandleInput(String::new()));
19164    cx.assert_editor_state(
19165        &r#"ˇone
19166        two
19167
19168        three
19169        fourˇ
19170        five
19171
19172        siˇx"#
19173            .unindent(),
19174    );
19175
19176    cx.dispatch_action(HandleInput("AAAA".to_string()));
19177    cx.assert_editor_state(
19178        &r#"AAAAˇone
19179        two
19180
19181        three
19182        fourAAAAˇ
19183        five
19184
19185        siAAAAˇx"#
19186            .unindent(),
19187    );
19188}
19189
19190#[gpui::test]
19191async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19192    init_test(cx, |_| {});
19193
19194    let mut cx = EditorTestContext::new(cx).await;
19195    cx.set_state(
19196        r#"let foo = 1;
19197let foo = 2;
19198let foo = 3;
19199let fooˇ = 4;
19200let foo = 5;
19201let foo = 6;
19202let foo = 7;
19203let foo = 8;
19204let foo = 9;
19205let foo = 10;
19206let foo = 11;
19207let foo = 12;
19208let foo = 13;
19209let foo = 14;
19210let foo = 15;"#,
19211    );
19212
19213    cx.update_editor(|e, window, cx| {
19214        assert_eq!(
19215            e.next_scroll_position,
19216            NextScrollCursorCenterTopBottom::Center,
19217            "Default next scroll direction is center",
19218        );
19219
19220        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19221        assert_eq!(
19222            e.next_scroll_position,
19223            NextScrollCursorCenterTopBottom::Top,
19224            "After center, next scroll direction should be top",
19225        );
19226
19227        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19228        assert_eq!(
19229            e.next_scroll_position,
19230            NextScrollCursorCenterTopBottom::Bottom,
19231            "After top, next scroll direction should be bottom",
19232        );
19233
19234        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19235        assert_eq!(
19236            e.next_scroll_position,
19237            NextScrollCursorCenterTopBottom::Center,
19238            "After bottom, scrolling should start over",
19239        );
19240
19241        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19242        assert_eq!(
19243            e.next_scroll_position,
19244            NextScrollCursorCenterTopBottom::Top,
19245            "Scrolling continues if retriggered fast enough"
19246        );
19247    });
19248
19249    cx.executor()
19250        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
19251    cx.executor().run_until_parked();
19252    cx.update_editor(|e, _, _| {
19253        assert_eq!(
19254            e.next_scroll_position,
19255            NextScrollCursorCenterTopBottom::Center,
19256            "If scrolling is not triggered fast enough, it should reset"
19257        );
19258    });
19259}
19260
19261#[gpui::test]
19262async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
19263    init_test(cx, |_| {});
19264    let mut cx = EditorLspTestContext::new_rust(
19265        lsp::ServerCapabilities {
19266            definition_provider: Some(lsp::OneOf::Left(true)),
19267            references_provider: Some(lsp::OneOf::Left(true)),
19268            ..lsp::ServerCapabilities::default()
19269        },
19270        cx,
19271    )
19272    .await;
19273
19274    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
19275        let go_to_definition = cx
19276            .lsp
19277            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19278                move |params, _| async move {
19279                    if empty_go_to_definition {
19280                        Ok(None)
19281                    } else {
19282                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
19283                            uri: params.text_document_position_params.text_document.uri,
19284                            range: lsp::Range::new(
19285                                lsp::Position::new(4, 3),
19286                                lsp::Position::new(4, 6),
19287                            ),
19288                        })))
19289                    }
19290                },
19291            );
19292        let references = cx
19293            .lsp
19294            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
19295                Ok(Some(vec![lsp::Location {
19296                    uri: params.text_document_position.text_document.uri,
19297                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
19298                }]))
19299            });
19300        (go_to_definition, references)
19301    };
19302
19303    cx.set_state(
19304        &r#"fn one() {
19305            let mut a = ˇtwo();
19306        }
19307
19308        fn two() {}"#
19309            .unindent(),
19310    );
19311    set_up_lsp_handlers(false, &mut cx);
19312    let navigated = cx
19313        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19314        .await
19315        .expect("Failed to navigate to definition");
19316    assert_eq!(
19317        navigated,
19318        Navigated::Yes,
19319        "Should have navigated to definition from the GetDefinition response"
19320    );
19321    cx.assert_editor_state(
19322        &r#"fn one() {
19323            let mut a = two();
19324        }
19325
19326        fn «twoˇ»() {}"#
19327            .unindent(),
19328    );
19329
19330    let editors = cx.update_workspace(|workspace, _, cx| {
19331        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19332    });
19333    cx.update_editor(|_, _, test_editor_cx| {
19334        assert_eq!(
19335            editors.len(),
19336            1,
19337            "Initially, only one, test, editor should be open in the workspace"
19338        );
19339        assert_eq!(
19340            test_editor_cx.entity(),
19341            editors.last().expect("Asserted len is 1").clone()
19342        );
19343    });
19344
19345    set_up_lsp_handlers(true, &mut cx);
19346    let navigated = cx
19347        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19348        .await
19349        .expect("Failed to navigate to lookup references");
19350    assert_eq!(
19351        navigated,
19352        Navigated::Yes,
19353        "Should have navigated to references as a fallback after empty GoToDefinition response"
19354    );
19355    // We should not change the selections in the existing file,
19356    // if opening another milti buffer with the references
19357    cx.assert_editor_state(
19358        &r#"fn one() {
19359            let mut a = two();
19360        }
19361
19362        fn «twoˇ»() {}"#
19363            .unindent(),
19364    );
19365    let editors = cx.update_workspace(|workspace, _, cx| {
19366        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19367    });
19368    cx.update_editor(|_, _, test_editor_cx| {
19369        assert_eq!(
19370            editors.len(),
19371            2,
19372            "After falling back to references search, we open a new editor with the results"
19373        );
19374        let references_fallback_text = editors
19375            .into_iter()
19376            .find(|new_editor| *new_editor != test_editor_cx.entity())
19377            .expect("Should have one non-test editor now")
19378            .read(test_editor_cx)
19379            .text(test_editor_cx);
19380        assert_eq!(
19381            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
19382            "Should use the range from the references response and not the GoToDefinition one"
19383        );
19384    });
19385}
19386
19387#[gpui::test]
19388async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
19389    init_test(cx, |_| {});
19390    cx.update(|cx| {
19391        let mut editor_settings = EditorSettings::get_global(cx).clone();
19392        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
19393        EditorSettings::override_global(editor_settings, cx);
19394    });
19395    let mut cx = EditorLspTestContext::new_rust(
19396        lsp::ServerCapabilities {
19397            definition_provider: Some(lsp::OneOf::Left(true)),
19398            references_provider: Some(lsp::OneOf::Left(true)),
19399            ..lsp::ServerCapabilities::default()
19400        },
19401        cx,
19402    )
19403    .await;
19404    let original_state = r#"fn one() {
19405        let mut a = ˇtwo();
19406    }
19407
19408    fn two() {}"#
19409        .unindent();
19410    cx.set_state(&original_state);
19411
19412    let mut go_to_definition = cx
19413        .lsp
19414        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
19415            move |_, _| async move { Ok(None) },
19416        );
19417    let _references = cx
19418        .lsp
19419        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
19420            panic!("Should not call for references with no go to definition fallback")
19421        });
19422
19423    let navigated = cx
19424        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
19425        .await
19426        .expect("Failed to navigate to lookup references");
19427    go_to_definition
19428        .next()
19429        .await
19430        .expect("Should have called the go_to_definition handler");
19431
19432    assert_eq!(
19433        navigated,
19434        Navigated::No,
19435        "Should have navigated to references as a fallback after empty GoToDefinition response"
19436    );
19437    cx.assert_editor_state(&original_state);
19438    let editors = cx.update_workspace(|workspace, _, cx| {
19439        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
19440    });
19441    cx.update_editor(|_, _, _| {
19442        assert_eq!(
19443            editors.len(),
19444            1,
19445            "After unsuccessful fallback, no other editor should have been opened"
19446        );
19447    });
19448}
19449
19450#[gpui::test]
19451async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
19452    init_test(cx, |_| {});
19453
19454    let language = Arc::new(Language::new(
19455        LanguageConfig::default(),
19456        Some(tree_sitter_rust::LANGUAGE.into()),
19457    ));
19458
19459    let text = r#"
19460        #[cfg(test)]
19461        mod tests() {
19462            #[test]
19463            fn runnable_1() {
19464                let a = 1;
19465            }
19466
19467            #[test]
19468            fn runnable_2() {
19469                let a = 1;
19470                let b = 2;
19471            }
19472        }
19473    "#
19474    .unindent();
19475
19476    let fs = FakeFs::new(cx.executor());
19477    fs.insert_file("/file.rs", Default::default()).await;
19478
19479    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19480    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19481    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19482    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
19483    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
19484
19485    let editor = cx.new_window_entity(|window, cx| {
19486        Editor::new(
19487            EditorMode::full(),
19488            multi_buffer,
19489            Some(project.clone()),
19490            window,
19491            cx,
19492        )
19493    });
19494
19495    editor.update_in(cx, |editor, window, cx| {
19496        let snapshot = editor.buffer().read(cx).snapshot(cx);
19497        editor.tasks.insert(
19498            (buffer.read(cx).remote_id(), 3),
19499            RunnableTasks {
19500                templates: vec![],
19501                offset: snapshot.anchor_before(43),
19502                column: 0,
19503                extra_variables: HashMap::default(),
19504                context_range: BufferOffset(43)..BufferOffset(85),
19505            },
19506        );
19507        editor.tasks.insert(
19508            (buffer.read(cx).remote_id(), 8),
19509            RunnableTasks {
19510                templates: vec![],
19511                offset: snapshot.anchor_before(86),
19512                column: 0,
19513                extra_variables: HashMap::default(),
19514                context_range: BufferOffset(86)..BufferOffset(191),
19515            },
19516        );
19517
19518        // Test finding task when cursor is inside function body
19519        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19520            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
19521        });
19522        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19523        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
19524
19525        // Test finding task when cursor is on function name
19526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19527            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
19528        });
19529        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
19530        assert_eq!(row, 8, "Should find task when cursor is on function name");
19531    });
19532}
19533
19534#[gpui::test]
19535async fn test_folding_buffers(cx: &mut TestAppContext) {
19536    init_test(cx, |_| {});
19537
19538    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19539    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
19540    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
19541
19542    let fs = FakeFs::new(cx.executor());
19543    fs.insert_tree(
19544        path!("/a"),
19545        json!({
19546            "first.rs": sample_text_1,
19547            "second.rs": sample_text_2,
19548            "third.rs": sample_text_3,
19549        }),
19550    )
19551    .await;
19552    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19553    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19554    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19555    let worktree = project.update(cx, |project, cx| {
19556        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19557        assert_eq!(worktrees.len(), 1);
19558        worktrees.pop().unwrap()
19559    });
19560    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19561
19562    let buffer_1 = project
19563        .update(cx, |project, cx| {
19564            project.open_buffer((worktree_id, "first.rs"), cx)
19565        })
19566        .await
19567        .unwrap();
19568    let buffer_2 = project
19569        .update(cx, |project, cx| {
19570            project.open_buffer((worktree_id, "second.rs"), cx)
19571        })
19572        .await
19573        .unwrap();
19574    let buffer_3 = project
19575        .update(cx, |project, cx| {
19576            project.open_buffer((worktree_id, "third.rs"), cx)
19577        })
19578        .await
19579        .unwrap();
19580
19581    let multi_buffer = cx.new(|cx| {
19582        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19583        multi_buffer.push_excerpts(
19584            buffer_1.clone(),
19585            [
19586                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19587                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19588                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19589            ],
19590            cx,
19591        );
19592        multi_buffer.push_excerpts(
19593            buffer_2.clone(),
19594            [
19595                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19596                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19597                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19598            ],
19599            cx,
19600        );
19601        multi_buffer.push_excerpts(
19602            buffer_3.clone(),
19603            [
19604                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19605                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19606                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19607            ],
19608            cx,
19609        );
19610        multi_buffer
19611    });
19612    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19613        Editor::new(
19614            EditorMode::full(),
19615            multi_buffer.clone(),
19616            Some(project.clone()),
19617            window,
19618            cx,
19619        )
19620    });
19621
19622    assert_eq!(
19623        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19624        "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19625    );
19626
19627    multi_buffer_editor.update(cx, |editor, cx| {
19628        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19629    });
19630    assert_eq!(
19631        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19632        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19633        "After folding the first buffer, its text should not be displayed"
19634    );
19635
19636    multi_buffer_editor.update(cx, |editor, cx| {
19637        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19638    });
19639    assert_eq!(
19640        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19641        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19642        "After folding the second buffer, its text should not be displayed"
19643    );
19644
19645    multi_buffer_editor.update(cx, |editor, cx| {
19646        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19647    });
19648    assert_eq!(
19649        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19650        "\n\n\n\n\n",
19651        "After folding the third buffer, its text should not be displayed"
19652    );
19653
19654    // Emulate selection inside the fold logic, that should work
19655    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19656        editor
19657            .snapshot(window, cx)
19658            .next_line_boundary(Point::new(0, 4));
19659    });
19660
19661    multi_buffer_editor.update(cx, |editor, cx| {
19662        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19663    });
19664    assert_eq!(
19665        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19666        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19667        "After unfolding the second buffer, its text should be displayed"
19668    );
19669
19670    // Typing inside of buffer 1 causes that buffer to be unfolded.
19671    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19672        assert_eq!(
19673            multi_buffer
19674                .read(cx)
19675                .snapshot(cx)
19676                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
19677                .collect::<String>(),
19678            "bbbb"
19679        );
19680        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
19681            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
19682        });
19683        editor.handle_input("B", window, cx);
19684    });
19685
19686    assert_eq!(
19687        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19688        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
19689        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
19690    );
19691
19692    multi_buffer_editor.update(cx, |editor, cx| {
19693        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19694    });
19695    assert_eq!(
19696        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19697        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
19698        "After unfolding the all buffers, all original text should be displayed"
19699    );
19700}
19701
19702#[gpui::test]
19703async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
19704    init_test(cx, |_| {});
19705
19706    let sample_text_1 = "1111\n2222\n3333".to_string();
19707    let sample_text_2 = "4444\n5555\n6666".to_string();
19708    let sample_text_3 = "7777\n8888\n9999".to_string();
19709
19710    let fs = FakeFs::new(cx.executor());
19711    fs.insert_tree(
19712        path!("/a"),
19713        json!({
19714            "first.rs": sample_text_1,
19715            "second.rs": sample_text_2,
19716            "third.rs": sample_text_3,
19717        }),
19718    )
19719    .await;
19720    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19721    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19722    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19723    let worktree = project.update(cx, |project, cx| {
19724        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19725        assert_eq!(worktrees.len(), 1);
19726        worktrees.pop().unwrap()
19727    });
19728    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19729
19730    let buffer_1 = project
19731        .update(cx, |project, cx| {
19732            project.open_buffer((worktree_id, "first.rs"), cx)
19733        })
19734        .await
19735        .unwrap();
19736    let buffer_2 = project
19737        .update(cx, |project, cx| {
19738            project.open_buffer((worktree_id, "second.rs"), cx)
19739        })
19740        .await
19741        .unwrap();
19742    let buffer_3 = project
19743        .update(cx, |project, cx| {
19744            project.open_buffer((worktree_id, "third.rs"), cx)
19745        })
19746        .await
19747        .unwrap();
19748
19749    let multi_buffer = cx.new(|cx| {
19750        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19751        multi_buffer.push_excerpts(
19752            buffer_1.clone(),
19753            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19754            cx,
19755        );
19756        multi_buffer.push_excerpts(
19757            buffer_2.clone(),
19758            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19759            cx,
19760        );
19761        multi_buffer.push_excerpts(
19762            buffer_3.clone(),
19763            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
19764            cx,
19765        );
19766        multi_buffer
19767    });
19768
19769    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19770        Editor::new(
19771            EditorMode::full(),
19772            multi_buffer,
19773            Some(project.clone()),
19774            window,
19775            cx,
19776        )
19777    });
19778
19779    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
19780    assert_eq!(
19781        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19782        full_text,
19783    );
19784
19785    multi_buffer_editor.update(cx, |editor, cx| {
19786        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
19787    });
19788    assert_eq!(
19789        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19790        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
19791        "After folding the first buffer, its text should not be displayed"
19792    );
19793
19794    multi_buffer_editor.update(cx, |editor, cx| {
19795        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
19796    });
19797
19798    assert_eq!(
19799        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19800        "\n\n\n\n\n\n7777\n8888\n9999",
19801        "After folding the second buffer, its text should not be displayed"
19802    );
19803
19804    multi_buffer_editor.update(cx, |editor, cx| {
19805        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
19806    });
19807    assert_eq!(
19808        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19809        "\n\n\n\n\n",
19810        "After folding the third buffer, its text should not be displayed"
19811    );
19812
19813    multi_buffer_editor.update(cx, |editor, cx| {
19814        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
19815    });
19816    assert_eq!(
19817        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19818        "\n\n\n\n4444\n5555\n6666\n\n",
19819        "After unfolding the second buffer, its text should be displayed"
19820    );
19821
19822    multi_buffer_editor.update(cx, |editor, cx| {
19823        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
19824    });
19825    assert_eq!(
19826        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19827        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
19828        "After unfolding the first buffer, its text should be displayed"
19829    );
19830
19831    multi_buffer_editor.update(cx, |editor, cx| {
19832        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
19833    });
19834    assert_eq!(
19835        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19836        full_text,
19837        "After unfolding all buffers, all original text should be displayed"
19838    );
19839}
19840
19841#[gpui::test]
19842async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
19843    init_test(cx, |_| {});
19844
19845    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
19846
19847    let fs = FakeFs::new(cx.executor());
19848    fs.insert_tree(
19849        path!("/a"),
19850        json!({
19851            "main.rs": sample_text,
19852        }),
19853    )
19854    .await;
19855    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19856    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19857    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19858    let worktree = project.update(cx, |project, cx| {
19859        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
19860        assert_eq!(worktrees.len(), 1);
19861        worktrees.pop().unwrap()
19862    });
19863    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
19864
19865    let buffer_1 = project
19866        .update(cx, |project, cx| {
19867            project.open_buffer((worktree_id, "main.rs"), cx)
19868        })
19869        .await
19870        .unwrap();
19871
19872    let multi_buffer = cx.new(|cx| {
19873        let mut multi_buffer = MultiBuffer::new(ReadWrite);
19874        multi_buffer.push_excerpts(
19875            buffer_1.clone(),
19876            [ExcerptRange::new(
19877                Point::new(0, 0)
19878                    ..Point::new(
19879                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
19880                        0,
19881                    ),
19882            )],
19883            cx,
19884        );
19885        multi_buffer
19886    });
19887    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19888        Editor::new(
19889            EditorMode::full(),
19890            multi_buffer,
19891            Some(project.clone()),
19892            window,
19893            cx,
19894        )
19895    });
19896
19897    let selection_range = Point::new(1, 0)..Point::new(2, 0);
19898    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19899        enum TestHighlight {}
19900        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
19901        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
19902        editor.highlight_text::<TestHighlight>(
19903            vec![highlight_range.clone()],
19904            HighlightStyle::color(Hsla::green()),
19905            cx,
19906        );
19907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19908            s.select_ranges(Some(highlight_range))
19909        });
19910    });
19911
19912    let full_text = format!("\n\n{sample_text}");
19913    assert_eq!(
19914        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
19915        full_text,
19916    );
19917}
19918
19919#[gpui::test]
19920async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
19921    init_test(cx, |_| {});
19922    cx.update(|cx| {
19923        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
19924            "keymaps/default-linux.json",
19925            cx,
19926        )
19927        .unwrap();
19928        cx.bind_keys(default_key_bindings);
19929    });
19930
19931    let (editor, cx) = cx.add_window_view(|window, cx| {
19932        let multi_buffer = MultiBuffer::build_multi(
19933            [
19934                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
19935                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
19936                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
19937                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
19938            ],
19939            cx,
19940        );
19941        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
19942
19943        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
19944        // fold all but the second buffer, so that we test navigating between two
19945        // adjacent folded buffers, as well as folded buffers at the start and
19946        // end the multibuffer
19947        editor.fold_buffer(buffer_ids[0], cx);
19948        editor.fold_buffer(buffer_ids[2], cx);
19949        editor.fold_buffer(buffer_ids[3], cx);
19950
19951        editor
19952    });
19953    cx.simulate_resize(size(px(1000.), px(1000.)));
19954
19955    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
19956    cx.assert_excerpts_with_selections(indoc! {"
19957        [EXCERPT]
19958        ˇ[FOLDED]
19959        [EXCERPT]
19960        a1
19961        b1
19962        [EXCERPT]
19963        [FOLDED]
19964        [EXCERPT]
19965        [FOLDED]
19966        "
19967    });
19968    cx.simulate_keystroke("down");
19969    cx.assert_excerpts_with_selections(indoc! {"
19970        [EXCERPT]
19971        [FOLDED]
19972        [EXCERPT]
19973        ˇa1
19974        b1
19975        [EXCERPT]
19976        [FOLDED]
19977        [EXCERPT]
19978        [FOLDED]
19979        "
19980    });
19981    cx.simulate_keystroke("down");
19982    cx.assert_excerpts_with_selections(indoc! {"
19983        [EXCERPT]
19984        [FOLDED]
19985        [EXCERPT]
19986        a1
19987        ˇb1
19988        [EXCERPT]
19989        [FOLDED]
19990        [EXCERPT]
19991        [FOLDED]
19992        "
19993    });
19994    cx.simulate_keystroke("down");
19995    cx.assert_excerpts_with_selections(indoc! {"
19996        [EXCERPT]
19997        [FOLDED]
19998        [EXCERPT]
19999        a1
20000        b1
20001        ˇ[EXCERPT]
20002        [FOLDED]
20003        [EXCERPT]
20004        [FOLDED]
20005        "
20006    });
20007    cx.simulate_keystroke("down");
20008    cx.assert_excerpts_with_selections(indoc! {"
20009        [EXCERPT]
20010        [FOLDED]
20011        [EXCERPT]
20012        a1
20013        b1
20014        [EXCERPT]
20015        ˇ[FOLDED]
20016        [EXCERPT]
20017        [FOLDED]
20018        "
20019    });
20020    for _ in 0..5 {
20021        cx.simulate_keystroke("down");
20022        cx.assert_excerpts_with_selections(indoc! {"
20023            [EXCERPT]
20024            [FOLDED]
20025            [EXCERPT]
20026            a1
20027            b1
20028            [EXCERPT]
20029            [FOLDED]
20030            [EXCERPT]
20031            ˇ[FOLDED]
20032            "
20033        });
20034    }
20035
20036    cx.simulate_keystroke("up");
20037    cx.assert_excerpts_with_selections(indoc! {"
20038        [EXCERPT]
20039        [FOLDED]
20040        [EXCERPT]
20041        a1
20042        b1
20043        [EXCERPT]
20044        ˇ[FOLDED]
20045        [EXCERPT]
20046        [FOLDED]
20047        "
20048    });
20049    cx.simulate_keystroke("up");
20050    cx.assert_excerpts_with_selections(indoc! {"
20051        [EXCERPT]
20052        [FOLDED]
20053        [EXCERPT]
20054        a1
20055        b1
20056        ˇ[EXCERPT]
20057        [FOLDED]
20058        [EXCERPT]
20059        [FOLDED]
20060        "
20061    });
20062    cx.simulate_keystroke("up");
20063    cx.assert_excerpts_with_selections(indoc! {"
20064        [EXCERPT]
20065        [FOLDED]
20066        [EXCERPT]
20067        a1
20068        ˇb1
20069        [EXCERPT]
20070        [FOLDED]
20071        [EXCERPT]
20072        [FOLDED]
20073        "
20074    });
20075    cx.simulate_keystroke("up");
20076    cx.assert_excerpts_with_selections(indoc! {"
20077        [EXCERPT]
20078        [FOLDED]
20079        [EXCERPT]
20080        ˇa1
20081        b1
20082        [EXCERPT]
20083        [FOLDED]
20084        [EXCERPT]
20085        [FOLDED]
20086        "
20087    });
20088    for _ in 0..5 {
20089        cx.simulate_keystroke("up");
20090        cx.assert_excerpts_with_selections(indoc! {"
20091            [EXCERPT]
20092            ˇ[FOLDED]
20093            [EXCERPT]
20094            a1
20095            b1
20096            [EXCERPT]
20097            [FOLDED]
20098            [EXCERPT]
20099            [FOLDED]
20100            "
20101        });
20102    }
20103}
20104
20105#[gpui::test]
20106async fn test_inline_completion_text(cx: &mut TestAppContext) {
20107    init_test(cx, |_| {});
20108
20109    // Simple insertion
20110    assert_highlighted_edits(
20111        "Hello, world!",
20112        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20113        true,
20114        cx,
20115        |highlighted_edits, cx| {
20116            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20117            assert_eq!(highlighted_edits.highlights.len(), 1);
20118            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20119            assert_eq!(
20120                highlighted_edits.highlights[0].1.background_color,
20121                Some(cx.theme().status().created_background)
20122            );
20123        },
20124    )
20125    .await;
20126
20127    // Replacement
20128    assert_highlighted_edits(
20129        "This is a test.",
20130        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20131        false,
20132        cx,
20133        |highlighted_edits, cx| {
20134            assert_eq!(highlighted_edits.text, "That is a test.");
20135            assert_eq!(highlighted_edits.highlights.len(), 1);
20136            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20137            assert_eq!(
20138                highlighted_edits.highlights[0].1.background_color,
20139                Some(cx.theme().status().created_background)
20140            );
20141        },
20142    )
20143    .await;
20144
20145    // Multiple edits
20146    assert_highlighted_edits(
20147        "Hello, world!",
20148        vec![
20149            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20150            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20151        ],
20152        false,
20153        cx,
20154        |highlighted_edits, cx| {
20155            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20156            assert_eq!(highlighted_edits.highlights.len(), 2);
20157            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20158            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20159            assert_eq!(
20160                highlighted_edits.highlights[0].1.background_color,
20161                Some(cx.theme().status().created_background)
20162            );
20163            assert_eq!(
20164                highlighted_edits.highlights[1].1.background_color,
20165                Some(cx.theme().status().created_background)
20166            );
20167        },
20168    )
20169    .await;
20170
20171    // Multiple lines with edits
20172    assert_highlighted_edits(
20173        "First line\nSecond line\nThird line\nFourth line",
20174        vec![
20175            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20176            (
20177                Point::new(2, 0)..Point::new(2, 10),
20178                "New third line".to_string(),
20179            ),
20180            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20181        ],
20182        false,
20183        cx,
20184        |highlighted_edits, cx| {
20185            assert_eq!(
20186                highlighted_edits.text,
20187                "Second modified\nNew third line\nFourth updated line"
20188            );
20189            assert_eq!(highlighted_edits.highlights.len(), 3);
20190            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20191            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20192            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20193            for highlight in &highlighted_edits.highlights {
20194                assert_eq!(
20195                    highlight.1.background_color,
20196                    Some(cx.theme().status().created_background)
20197                );
20198            }
20199        },
20200    )
20201    .await;
20202}
20203
20204#[gpui::test]
20205async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
20206    init_test(cx, |_| {});
20207
20208    // Deletion
20209    assert_highlighted_edits(
20210        "Hello, world!",
20211        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20212        true,
20213        cx,
20214        |highlighted_edits, cx| {
20215            assert_eq!(highlighted_edits.text, "Hello, world!");
20216            assert_eq!(highlighted_edits.highlights.len(), 1);
20217            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20218            assert_eq!(
20219                highlighted_edits.highlights[0].1.background_color,
20220                Some(cx.theme().status().deleted_background)
20221            );
20222        },
20223    )
20224    .await;
20225
20226    // Insertion
20227    assert_highlighted_edits(
20228        "Hello, world!",
20229        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
20230        true,
20231        cx,
20232        |highlighted_edits, cx| {
20233            assert_eq!(highlighted_edits.highlights.len(), 1);
20234            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
20235            assert_eq!(
20236                highlighted_edits.highlights[0].1.background_color,
20237                Some(cx.theme().status().created_background)
20238            );
20239        },
20240    )
20241    .await;
20242}
20243
20244async fn assert_highlighted_edits(
20245    text: &str,
20246    edits: Vec<(Range<Point>, String)>,
20247    include_deletions: bool,
20248    cx: &mut TestAppContext,
20249    assertion_fn: impl Fn(HighlightedText, &App),
20250) {
20251    let window = cx.add_window(|window, cx| {
20252        let buffer = MultiBuffer::build_simple(text, cx);
20253        Editor::new(EditorMode::full(), buffer, None, window, cx)
20254    });
20255    let cx = &mut VisualTestContext::from_window(*window, cx);
20256
20257    let (buffer, snapshot) = window
20258        .update(cx, |editor, _window, cx| {
20259            (
20260                editor.buffer().clone(),
20261                editor.buffer().read(cx).snapshot(cx),
20262            )
20263        })
20264        .unwrap();
20265
20266    let edits = edits
20267        .into_iter()
20268        .map(|(range, edit)| {
20269            (
20270                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
20271                edit,
20272            )
20273        })
20274        .collect::<Vec<_>>();
20275
20276    let text_anchor_edits = edits
20277        .clone()
20278        .into_iter()
20279        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
20280        .collect::<Vec<_>>();
20281
20282    let edit_preview = window
20283        .update(cx, |_, _window, cx| {
20284            buffer
20285                .read(cx)
20286                .as_singleton()
20287                .unwrap()
20288                .read(cx)
20289                .preview_edits(text_anchor_edits.into(), cx)
20290        })
20291        .unwrap()
20292        .await;
20293
20294    cx.update(|_window, cx| {
20295        let highlighted_edits = inline_completion_edit_text(
20296            &snapshot.as_singleton().unwrap().2,
20297            &edits,
20298            &edit_preview,
20299            include_deletions,
20300            cx,
20301        );
20302        assertion_fn(highlighted_edits, cx)
20303    });
20304}
20305
20306#[track_caller]
20307fn assert_breakpoint(
20308    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
20309    path: &Arc<Path>,
20310    expected: Vec<(u32, Breakpoint)>,
20311) {
20312    if expected.len() == 0usize {
20313        assert!(!breakpoints.contains_key(path), "{}", path.display());
20314    } else {
20315        let mut breakpoint = breakpoints
20316            .get(path)
20317            .unwrap()
20318            .into_iter()
20319            .map(|breakpoint| {
20320                (
20321                    breakpoint.row,
20322                    Breakpoint {
20323                        message: breakpoint.message.clone(),
20324                        state: breakpoint.state,
20325                        condition: breakpoint.condition.clone(),
20326                        hit_condition: breakpoint.hit_condition.clone(),
20327                    },
20328                )
20329            })
20330            .collect::<Vec<_>>();
20331
20332        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
20333
20334        assert_eq!(expected, breakpoint);
20335    }
20336}
20337
20338fn add_log_breakpoint_at_cursor(
20339    editor: &mut Editor,
20340    log_message: &str,
20341    window: &mut Window,
20342    cx: &mut Context<Editor>,
20343) {
20344    let (anchor, bp) = editor
20345        .breakpoints_at_cursors(window, cx)
20346        .first()
20347        .and_then(|(anchor, bp)| {
20348            if let Some(bp) = bp {
20349                Some((*anchor, bp.clone()))
20350            } else {
20351                None
20352            }
20353        })
20354        .unwrap_or_else(|| {
20355            let cursor_position: Point = editor.selections.newest(cx).head();
20356
20357            let breakpoint_position = editor
20358                .snapshot(window, cx)
20359                .display_snapshot
20360                .buffer_snapshot
20361                .anchor_before(Point::new(cursor_position.row, 0));
20362
20363            (breakpoint_position, Breakpoint::new_log(&log_message))
20364        });
20365
20366    editor.edit_breakpoint_at_anchor(
20367        anchor,
20368        bp,
20369        BreakpointEditAction::EditLogMessage(log_message.into()),
20370        cx,
20371    );
20372}
20373
20374#[gpui::test]
20375async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
20376    init_test(cx, |_| {});
20377
20378    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20379    let fs = FakeFs::new(cx.executor());
20380    fs.insert_tree(
20381        path!("/a"),
20382        json!({
20383            "main.rs": sample_text,
20384        }),
20385    )
20386    .await;
20387    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20388    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20389    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20390
20391    let fs = FakeFs::new(cx.executor());
20392    fs.insert_tree(
20393        path!("/a"),
20394        json!({
20395            "main.rs": sample_text,
20396        }),
20397    )
20398    .await;
20399    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20400    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20401    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20402    let worktree_id = workspace
20403        .update(cx, |workspace, _window, cx| {
20404            workspace.project().update(cx, |project, cx| {
20405                project.worktrees(cx).next().unwrap().read(cx).id()
20406            })
20407        })
20408        .unwrap();
20409
20410    let buffer = project
20411        .update(cx, |project, cx| {
20412            project.open_buffer((worktree_id, "main.rs"), cx)
20413        })
20414        .await
20415        .unwrap();
20416
20417    let (editor, cx) = cx.add_window_view(|window, cx| {
20418        Editor::new(
20419            EditorMode::full(),
20420            MultiBuffer::build_from_buffer(buffer, cx),
20421            Some(project.clone()),
20422            window,
20423            cx,
20424        )
20425    });
20426
20427    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20428    let abs_path = project.read_with(cx, |project, cx| {
20429        project
20430            .absolute_path(&project_path, cx)
20431            .map(|path_buf| Arc::from(path_buf.to_owned()))
20432            .unwrap()
20433    });
20434
20435    // assert we can add breakpoint on the first line
20436    editor.update_in(cx, |editor, window, cx| {
20437        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20438        editor.move_to_end(&MoveToEnd, window, cx);
20439        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20440    });
20441
20442    let breakpoints = editor.update(cx, |editor, cx| {
20443        editor
20444            .breakpoint_store()
20445            .as_ref()
20446            .unwrap()
20447            .read(cx)
20448            .all_source_breakpoints(cx)
20449            .clone()
20450    });
20451
20452    assert_eq!(1, breakpoints.len());
20453    assert_breakpoint(
20454        &breakpoints,
20455        &abs_path,
20456        vec![
20457            (0, Breakpoint::new_standard()),
20458            (3, Breakpoint::new_standard()),
20459        ],
20460    );
20461
20462    editor.update_in(cx, |editor, window, cx| {
20463        editor.move_to_beginning(&MoveToBeginning, window, cx);
20464        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20465    });
20466
20467    let breakpoints = editor.update(cx, |editor, cx| {
20468        editor
20469            .breakpoint_store()
20470            .as_ref()
20471            .unwrap()
20472            .read(cx)
20473            .all_source_breakpoints(cx)
20474            .clone()
20475    });
20476
20477    assert_eq!(1, breakpoints.len());
20478    assert_breakpoint(
20479        &breakpoints,
20480        &abs_path,
20481        vec![(3, Breakpoint::new_standard())],
20482    );
20483
20484    editor.update_in(cx, |editor, window, cx| {
20485        editor.move_to_end(&MoveToEnd, window, cx);
20486        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20487    });
20488
20489    let breakpoints = editor.update(cx, |editor, cx| {
20490        editor
20491            .breakpoint_store()
20492            .as_ref()
20493            .unwrap()
20494            .read(cx)
20495            .all_source_breakpoints(cx)
20496            .clone()
20497    });
20498
20499    assert_eq!(0, breakpoints.len());
20500    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20501}
20502
20503#[gpui::test]
20504async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
20505    init_test(cx, |_| {});
20506
20507    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20508
20509    let fs = FakeFs::new(cx.executor());
20510    fs.insert_tree(
20511        path!("/a"),
20512        json!({
20513            "main.rs": sample_text,
20514        }),
20515    )
20516    .await;
20517    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20518    let (workspace, cx) =
20519        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
20520
20521    let worktree_id = workspace.update(cx, |workspace, cx| {
20522        workspace.project().update(cx, |project, cx| {
20523            project.worktrees(cx).next().unwrap().read(cx).id()
20524        })
20525    });
20526
20527    let buffer = project
20528        .update(cx, |project, cx| {
20529            project.open_buffer((worktree_id, "main.rs"), cx)
20530        })
20531        .await
20532        .unwrap();
20533
20534    let (editor, cx) = cx.add_window_view(|window, cx| {
20535        Editor::new(
20536            EditorMode::full(),
20537            MultiBuffer::build_from_buffer(buffer, cx),
20538            Some(project.clone()),
20539            window,
20540            cx,
20541        )
20542    });
20543
20544    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20545    let abs_path = project.read_with(cx, |project, cx| {
20546        project
20547            .absolute_path(&project_path, cx)
20548            .map(|path_buf| Arc::from(path_buf.to_owned()))
20549            .unwrap()
20550    });
20551
20552    editor.update_in(cx, |editor, window, cx| {
20553        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20554    });
20555
20556    let breakpoints = editor.update(cx, |editor, cx| {
20557        editor
20558            .breakpoint_store()
20559            .as_ref()
20560            .unwrap()
20561            .read(cx)
20562            .all_source_breakpoints(cx)
20563            .clone()
20564    });
20565
20566    assert_breakpoint(
20567        &breakpoints,
20568        &abs_path,
20569        vec![(0, Breakpoint::new_log("hello world"))],
20570    );
20571
20572    // Removing a log message from a log breakpoint should remove it
20573    editor.update_in(cx, |editor, window, cx| {
20574        add_log_breakpoint_at_cursor(editor, "", window, cx);
20575    });
20576
20577    let breakpoints = editor.update(cx, |editor, cx| {
20578        editor
20579            .breakpoint_store()
20580            .as_ref()
20581            .unwrap()
20582            .read(cx)
20583            .all_source_breakpoints(cx)
20584            .clone()
20585    });
20586
20587    assert_breakpoint(&breakpoints, &abs_path, vec![]);
20588
20589    editor.update_in(cx, |editor, window, cx| {
20590        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20591        editor.move_to_end(&MoveToEnd, window, cx);
20592        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20593        // Not adding a log message to a standard breakpoint shouldn't remove it
20594        add_log_breakpoint_at_cursor(editor, "", window, cx);
20595    });
20596
20597    let breakpoints = editor.update(cx, |editor, cx| {
20598        editor
20599            .breakpoint_store()
20600            .as_ref()
20601            .unwrap()
20602            .read(cx)
20603            .all_source_breakpoints(cx)
20604            .clone()
20605    });
20606
20607    assert_breakpoint(
20608        &breakpoints,
20609        &abs_path,
20610        vec![
20611            (0, Breakpoint::new_standard()),
20612            (3, Breakpoint::new_standard()),
20613        ],
20614    );
20615
20616    editor.update_in(cx, |editor, window, cx| {
20617        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
20618    });
20619
20620    let breakpoints = editor.update(cx, |editor, cx| {
20621        editor
20622            .breakpoint_store()
20623            .as_ref()
20624            .unwrap()
20625            .read(cx)
20626            .all_source_breakpoints(cx)
20627            .clone()
20628    });
20629
20630    assert_breakpoint(
20631        &breakpoints,
20632        &abs_path,
20633        vec![
20634            (0, Breakpoint::new_standard()),
20635            (3, Breakpoint::new_log("hello world")),
20636        ],
20637    );
20638
20639    editor.update_in(cx, |editor, window, cx| {
20640        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
20641    });
20642
20643    let breakpoints = editor.update(cx, |editor, cx| {
20644        editor
20645            .breakpoint_store()
20646            .as_ref()
20647            .unwrap()
20648            .read(cx)
20649            .all_source_breakpoints(cx)
20650            .clone()
20651    });
20652
20653    assert_breakpoint(
20654        &breakpoints,
20655        &abs_path,
20656        vec![
20657            (0, Breakpoint::new_standard()),
20658            (3, Breakpoint::new_log("hello Earth!!")),
20659        ],
20660    );
20661}
20662
20663/// This also tests that Editor::breakpoint_at_cursor_head is working properly
20664/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
20665/// or when breakpoints were placed out of order. This tests for a regression too
20666#[gpui::test]
20667async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
20668    init_test(cx, |_| {});
20669
20670    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
20671    let fs = FakeFs::new(cx.executor());
20672    fs.insert_tree(
20673        path!("/a"),
20674        json!({
20675            "main.rs": sample_text,
20676        }),
20677    )
20678    .await;
20679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20680    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20681    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20682
20683    let fs = FakeFs::new(cx.executor());
20684    fs.insert_tree(
20685        path!("/a"),
20686        json!({
20687            "main.rs": sample_text,
20688        }),
20689    )
20690    .await;
20691    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20692    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20693    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20694    let worktree_id = workspace
20695        .update(cx, |workspace, _window, cx| {
20696            workspace.project().update(cx, |project, cx| {
20697                project.worktrees(cx).next().unwrap().read(cx).id()
20698            })
20699        })
20700        .unwrap();
20701
20702    let buffer = project
20703        .update(cx, |project, cx| {
20704            project.open_buffer((worktree_id, "main.rs"), cx)
20705        })
20706        .await
20707        .unwrap();
20708
20709    let (editor, cx) = cx.add_window_view(|window, cx| {
20710        Editor::new(
20711            EditorMode::full(),
20712            MultiBuffer::build_from_buffer(buffer, cx),
20713            Some(project.clone()),
20714            window,
20715            cx,
20716        )
20717    });
20718
20719    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
20720    let abs_path = project.read_with(cx, |project, cx| {
20721        project
20722            .absolute_path(&project_path, cx)
20723            .map(|path_buf| Arc::from(path_buf.to_owned()))
20724            .unwrap()
20725    });
20726
20727    // assert we can add breakpoint on the first line
20728    editor.update_in(cx, |editor, window, cx| {
20729        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20730        editor.move_to_end(&MoveToEnd, window, cx);
20731        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20732        editor.move_up(&MoveUp, window, cx);
20733        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
20734    });
20735
20736    let breakpoints = editor.update(cx, |editor, cx| {
20737        editor
20738            .breakpoint_store()
20739            .as_ref()
20740            .unwrap()
20741            .read(cx)
20742            .all_source_breakpoints(cx)
20743            .clone()
20744    });
20745
20746    assert_eq!(1, breakpoints.len());
20747    assert_breakpoint(
20748        &breakpoints,
20749        &abs_path,
20750        vec![
20751            (0, Breakpoint::new_standard()),
20752            (2, Breakpoint::new_standard()),
20753            (3, Breakpoint::new_standard()),
20754        ],
20755    );
20756
20757    editor.update_in(cx, |editor, window, cx| {
20758        editor.move_to_beginning(&MoveToBeginning, window, cx);
20759        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20760        editor.move_to_end(&MoveToEnd, window, cx);
20761        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20762        // Disabling a breakpoint that doesn't exist should do nothing
20763        editor.move_up(&MoveUp, window, cx);
20764        editor.move_up(&MoveUp, window, cx);
20765        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20766    });
20767
20768    let breakpoints = editor.update(cx, |editor, cx| {
20769        editor
20770            .breakpoint_store()
20771            .as_ref()
20772            .unwrap()
20773            .read(cx)
20774            .all_source_breakpoints(cx)
20775            .clone()
20776    });
20777
20778    let disable_breakpoint = {
20779        let mut bp = Breakpoint::new_standard();
20780        bp.state = BreakpointState::Disabled;
20781        bp
20782    };
20783
20784    assert_eq!(1, breakpoints.len());
20785    assert_breakpoint(
20786        &breakpoints,
20787        &abs_path,
20788        vec![
20789            (0, disable_breakpoint.clone()),
20790            (2, Breakpoint::new_standard()),
20791            (3, disable_breakpoint.clone()),
20792        ],
20793    );
20794
20795    editor.update_in(cx, |editor, window, cx| {
20796        editor.move_to_beginning(&MoveToBeginning, window, cx);
20797        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20798        editor.move_to_end(&MoveToEnd, window, cx);
20799        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
20800        editor.move_up(&MoveUp, window, cx);
20801        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
20802    });
20803
20804    let breakpoints = editor.update(cx, |editor, cx| {
20805        editor
20806            .breakpoint_store()
20807            .as_ref()
20808            .unwrap()
20809            .read(cx)
20810            .all_source_breakpoints(cx)
20811            .clone()
20812    });
20813
20814    assert_eq!(1, breakpoints.len());
20815    assert_breakpoint(
20816        &breakpoints,
20817        &abs_path,
20818        vec![
20819            (0, Breakpoint::new_standard()),
20820            (2, disable_breakpoint),
20821            (3, Breakpoint::new_standard()),
20822        ],
20823    );
20824}
20825
20826#[gpui::test]
20827async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
20828    init_test(cx, |_| {});
20829    let capabilities = lsp::ServerCapabilities {
20830        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
20831            prepare_provider: Some(true),
20832            work_done_progress_options: Default::default(),
20833        })),
20834        ..Default::default()
20835    };
20836    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20837
20838    cx.set_state(indoc! {"
20839        struct Fˇoo {}
20840    "});
20841
20842    cx.update_editor(|editor, _, cx| {
20843        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20844        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20845        editor.highlight_background::<DocumentHighlightRead>(
20846            &[highlight_range],
20847            |theme| theme.colors().editor_document_highlight_read_background,
20848            cx,
20849        );
20850    });
20851
20852    let mut prepare_rename_handler = cx
20853        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
20854            move |_, _, _| async move {
20855                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
20856                    start: lsp::Position {
20857                        line: 0,
20858                        character: 7,
20859                    },
20860                    end: lsp::Position {
20861                        line: 0,
20862                        character: 10,
20863                    },
20864                })))
20865            },
20866        );
20867    let prepare_rename_task = cx
20868        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20869        .expect("Prepare rename was not started");
20870    prepare_rename_handler.next().await.unwrap();
20871    prepare_rename_task.await.expect("Prepare rename failed");
20872
20873    let mut rename_handler =
20874        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20875            let edit = lsp::TextEdit {
20876                range: lsp::Range {
20877                    start: lsp::Position {
20878                        line: 0,
20879                        character: 7,
20880                    },
20881                    end: lsp::Position {
20882                        line: 0,
20883                        character: 10,
20884                    },
20885                },
20886                new_text: "FooRenamed".to_string(),
20887            };
20888            Ok(Some(lsp::WorkspaceEdit::new(
20889                // Specify the same edit twice
20890                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
20891            )))
20892        });
20893    let rename_task = cx
20894        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20895        .expect("Confirm rename was not started");
20896    rename_handler.next().await.unwrap();
20897    rename_task.await.expect("Confirm rename failed");
20898    cx.run_until_parked();
20899
20900    // Despite two edits, only one is actually applied as those are identical
20901    cx.assert_editor_state(indoc! {"
20902        struct FooRenamedˇ {}
20903    "});
20904}
20905
20906#[gpui::test]
20907async fn test_rename_without_prepare(cx: &mut TestAppContext) {
20908    init_test(cx, |_| {});
20909    // These capabilities indicate that the server does not support prepare rename.
20910    let capabilities = lsp::ServerCapabilities {
20911        rename_provider: Some(lsp::OneOf::Left(true)),
20912        ..Default::default()
20913    };
20914    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
20915
20916    cx.set_state(indoc! {"
20917        struct Fˇoo {}
20918    "});
20919
20920    cx.update_editor(|editor, _window, cx| {
20921        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
20922        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
20923        editor.highlight_background::<DocumentHighlightRead>(
20924            &[highlight_range],
20925            |theme| theme.colors().editor_document_highlight_read_background,
20926            cx,
20927        );
20928    });
20929
20930    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
20931        .expect("Prepare rename was not started")
20932        .await
20933        .expect("Prepare rename failed");
20934
20935    let mut rename_handler =
20936        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
20937            let edit = lsp::TextEdit {
20938                range: lsp::Range {
20939                    start: lsp::Position {
20940                        line: 0,
20941                        character: 7,
20942                    },
20943                    end: lsp::Position {
20944                        line: 0,
20945                        character: 10,
20946                    },
20947                },
20948                new_text: "FooRenamed".to_string(),
20949            };
20950            Ok(Some(lsp::WorkspaceEdit::new(
20951                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
20952            )))
20953        });
20954    let rename_task = cx
20955        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
20956        .expect("Confirm rename was not started");
20957    rename_handler.next().await.unwrap();
20958    rename_task.await.expect("Confirm rename failed");
20959    cx.run_until_parked();
20960
20961    // Correct range is renamed, as `surrounding_word` is used to find it.
20962    cx.assert_editor_state(indoc! {"
20963        struct FooRenamedˇ {}
20964    "});
20965}
20966
20967#[gpui::test]
20968async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
20969    init_test(cx, |_| {});
20970    let mut cx = EditorTestContext::new(cx).await;
20971
20972    let language = Arc::new(
20973        Language::new(
20974            LanguageConfig::default(),
20975            Some(tree_sitter_html::LANGUAGE.into()),
20976        )
20977        .with_brackets_query(
20978            r#"
20979            ("<" @open "/>" @close)
20980            ("</" @open ">" @close)
20981            ("<" @open ">" @close)
20982            ("\"" @open "\"" @close)
20983            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
20984        "#,
20985        )
20986        .unwrap(),
20987    );
20988    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
20989
20990    cx.set_state(indoc! {"
20991        <span>ˇ</span>
20992    "});
20993    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
20994    cx.assert_editor_state(indoc! {"
20995        <span>
20996        ˇ
20997        </span>
20998    "});
20999
21000    cx.set_state(indoc! {"
21001        <span><span></span>ˇ</span>
21002    "});
21003    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21004    cx.assert_editor_state(indoc! {"
21005        <span><span></span>
21006        ˇ</span>
21007    "});
21008
21009    cx.set_state(indoc! {"
21010        <span>ˇ
21011        </span>
21012    "});
21013    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21014    cx.assert_editor_state(indoc! {"
21015        <span>
21016        ˇ
21017        </span>
21018    "});
21019}
21020
21021#[gpui::test(iterations = 10)]
21022async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21023    init_test(cx, |_| {});
21024
21025    let fs = FakeFs::new(cx.executor());
21026    fs.insert_tree(
21027        path!("/dir"),
21028        json!({
21029            "a.ts": "a",
21030        }),
21031    )
21032    .await;
21033
21034    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21035    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21036    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21037
21038    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21039    language_registry.add(Arc::new(Language::new(
21040        LanguageConfig {
21041            name: "TypeScript".into(),
21042            matcher: LanguageMatcher {
21043                path_suffixes: vec!["ts".to_string()],
21044                ..Default::default()
21045            },
21046            ..Default::default()
21047        },
21048        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21049    )));
21050    let mut fake_language_servers = language_registry.register_fake_lsp(
21051        "TypeScript",
21052        FakeLspAdapter {
21053            capabilities: lsp::ServerCapabilities {
21054                code_lens_provider: Some(lsp::CodeLensOptions {
21055                    resolve_provider: Some(true),
21056                }),
21057                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21058                    commands: vec!["_the/command".to_string()],
21059                    ..lsp::ExecuteCommandOptions::default()
21060                }),
21061                ..lsp::ServerCapabilities::default()
21062            },
21063            ..FakeLspAdapter::default()
21064        },
21065    );
21066
21067    let (buffer, _handle) = project
21068        .update(cx, |p, cx| {
21069            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
21070        })
21071        .await
21072        .unwrap();
21073    cx.executor().run_until_parked();
21074
21075    let fake_server = fake_language_servers.next().await.unwrap();
21076
21077    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21078    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21079    drop(buffer_snapshot);
21080    let actions = cx
21081        .update_window(*workspace, |_, window, cx| {
21082            project.code_actions(&buffer, anchor..anchor, window, cx)
21083        })
21084        .unwrap();
21085
21086    fake_server
21087        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21088            Ok(Some(vec![
21089                lsp::CodeLens {
21090                    range: lsp::Range::default(),
21091                    command: Some(lsp::Command {
21092                        title: "Code lens command".to_owned(),
21093                        command: "_the/command".to_owned(),
21094                        arguments: None,
21095                    }),
21096                    data: None,
21097                },
21098                lsp::CodeLens {
21099                    range: lsp::Range::default(),
21100                    command: Some(lsp::Command {
21101                        title: "Command not in capabilities".to_owned(),
21102                        command: "not in capabilities".to_owned(),
21103                        arguments: None,
21104                    }),
21105                    data: None,
21106                },
21107                lsp::CodeLens {
21108                    range: lsp::Range {
21109                        start: lsp::Position {
21110                            line: 1,
21111                            character: 1,
21112                        },
21113                        end: lsp::Position {
21114                            line: 1,
21115                            character: 1,
21116                        },
21117                    },
21118                    command: Some(lsp::Command {
21119                        title: "Command not in range".to_owned(),
21120                        command: "_the/command".to_owned(),
21121                        arguments: None,
21122                    }),
21123                    data: None,
21124                },
21125            ]))
21126        })
21127        .next()
21128        .await;
21129
21130    let actions = actions.await.unwrap();
21131    assert_eq!(
21132        actions.len(),
21133        1,
21134        "Should have only one valid action for the 0..0 range"
21135    );
21136    let action = actions[0].clone();
21137    let apply = project.update(cx, |project, cx| {
21138        project.apply_code_action(buffer.clone(), action, true, cx)
21139    });
21140
21141    // Resolving the code action does not populate its edits. In absence of
21142    // edits, we must execute the given command.
21143    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21144        |mut lens, _| async move {
21145            let lens_command = lens.command.as_mut().expect("should have a command");
21146            assert_eq!(lens_command.title, "Code lens command");
21147            lens_command.arguments = Some(vec![json!("the-argument")]);
21148            Ok(lens)
21149        },
21150    );
21151
21152    // While executing the command, the language server sends the editor
21153    // a `workspaceEdit` request.
21154    fake_server
21155        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21156            let fake = fake_server.clone();
21157            move |params, _| {
21158                assert_eq!(params.command, "_the/command");
21159                let fake = fake.clone();
21160                async move {
21161                    fake.server
21162                        .request::<lsp::request::ApplyWorkspaceEdit>(
21163                            lsp::ApplyWorkspaceEditParams {
21164                                label: None,
21165                                edit: lsp::WorkspaceEdit {
21166                                    changes: Some(
21167                                        [(
21168                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21169                                            vec![lsp::TextEdit {
21170                                                range: lsp::Range::new(
21171                                                    lsp::Position::new(0, 0),
21172                                                    lsp::Position::new(0, 0),
21173                                                ),
21174                                                new_text: "X".into(),
21175                                            }],
21176                                        )]
21177                                        .into_iter()
21178                                        .collect(),
21179                                    ),
21180                                    ..Default::default()
21181                                },
21182                            },
21183                        )
21184                        .await
21185                        .into_response()
21186                        .unwrap();
21187                    Ok(Some(json!(null)))
21188                }
21189            }
21190        })
21191        .next()
21192        .await;
21193
21194    // Applying the code lens command returns a project transaction containing the edits
21195    // sent by the language server in its `workspaceEdit` request.
21196    let transaction = apply.await.unwrap();
21197    assert!(transaction.0.contains_key(&buffer));
21198    buffer.update(cx, |buffer, cx| {
21199        assert_eq!(buffer.text(), "Xa");
21200        buffer.undo(cx);
21201        assert_eq!(buffer.text(), "a");
21202    });
21203}
21204
21205#[gpui::test]
21206async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
21207    init_test(cx, |_| {});
21208
21209    let fs = FakeFs::new(cx.executor());
21210    let main_text = r#"fn main() {
21211println!("1");
21212println!("2");
21213println!("3");
21214println!("4");
21215println!("5");
21216}"#;
21217    let lib_text = "mod foo {}";
21218    fs.insert_tree(
21219        path!("/a"),
21220        json!({
21221            "lib.rs": lib_text,
21222            "main.rs": main_text,
21223        }),
21224    )
21225    .await;
21226
21227    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21228    let (workspace, cx) =
21229        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21230    let worktree_id = workspace.update(cx, |workspace, cx| {
21231        workspace.project().update(cx, |project, cx| {
21232            project.worktrees(cx).next().unwrap().read(cx).id()
21233        })
21234    });
21235
21236    let expected_ranges = vec![
21237        Point::new(0, 0)..Point::new(0, 0),
21238        Point::new(1, 0)..Point::new(1, 1),
21239        Point::new(2, 0)..Point::new(2, 2),
21240        Point::new(3, 0)..Point::new(3, 3),
21241    ];
21242
21243    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21244    let editor_1 = workspace
21245        .update_in(cx, |workspace, window, cx| {
21246            workspace.open_path(
21247                (worktree_id, "main.rs"),
21248                Some(pane_1.downgrade()),
21249                true,
21250                window,
21251                cx,
21252            )
21253        })
21254        .unwrap()
21255        .await
21256        .downcast::<Editor>()
21257        .unwrap();
21258    pane_1.update(cx, |pane, cx| {
21259        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21260        open_editor.update(cx, |editor, cx| {
21261            assert_eq!(
21262                editor.display_text(cx),
21263                main_text,
21264                "Original main.rs text on initial open",
21265            );
21266            assert_eq!(
21267                editor
21268                    .selections
21269                    .all::<Point>(cx)
21270                    .into_iter()
21271                    .map(|s| s.range())
21272                    .collect::<Vec<_>>(),
21273                vec![Point::zero()..Point::zero()],
21274                "Default selections on initial open",
21275            );
21276        })
21277    });
21278    editor_1.update_in(cx, |editor, window, cx| {
21279        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21280            s.select_ranges(expected_ranges.clone());
21281        });
21282    });
21283
21284    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
21285        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
21286    });
21287    let editor_2 = workspace
21288        .update_in(cx, |workspace, window, cx| {
21289            workspace.open_path(
21290                (worktree_id, "main.rs"),
21291                Some(pane_2.downgrade()),
21292                true,
21293                window,
21294                cx,
21295            )
21296        })
21297        .unwrap()
21298        .await
21299        .downcast::<Editor>()
21300        .unwrap();
21301    pane_2.update(cx, |pane, cx| {
21302        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21303        open_editor.update(cx, |editor, cx| {
21304            assert_eq!(
21305                editor.display_text(cx),
21306                main_text,
21307                "Original main.rs text on initial open in another panel",
21308            );
21309            assert_eq!(
21310                editor
21311                    .selections
21312                    .all::<Point>(cx)
21313                    .into_iter()
21314                    .map(|s| s.range())
21315                    .collect::<Vec<_>>(),
21316                vec![Point::zero()..Point::zero()],
21317                "Default selections on initial open in another panel",
21318            );
21319        })
21320    });
21321
21322    editor_2.update_in(cx, |editor, window, cx| {
21323        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
21324    });
21325
21326    let _other_editor_1 = workspace
21327        .update_in(cx, |workspace, window, cx| {
21328            workspace.open_path(
21329                (worktree_id, "lib.rs"),
21330                Some(pane_1.downgrade()),
21331                true,
21332                window,
21333                cx,
21334            )
21335        })
21336        .unwrap()
21337        .await
21338        .downcast::<Editor>()
21339        .unwrap();
21340    pane_1
21341        .update_in(cx, |pane, window, cx| {
21342            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21343        })
21344        .await
21345        .unwrap();
21346    drop(editor_1);
21347    pane_1.update(cx, |pane, cx| {
21348        pane.active_item()
21349            .unwrap()
21350            .downcast::<Editor>()
21351            .unwrap()
21352            .update(cx, |editor, cx| {
21353                assert_eq!(
21354                    editor.display_text(cx),
21355                    lib_text,
21356                    "Other file should be open and active",
21357                );
21358            });
21359        assert_eq!(pane.items().count(), 1, "No other editors should be open");
21360    });
21361
21362    let _other_editor_2 = workspace
21363        .update_in(cx, |workspace, window, cx| {
21364            workspace.open_path(
21365                (worktree_id, "lib.rs"),
21366                Some(pane_2.downgrade()),
21367                true,
21368                window,
21369                cx,
21370            )
21371        })
21372        .unwrap()
21373        .await
21374        .downcast::<Editor>()
21375        .unwrap();
21376    pane_2
21377        .update_in(cx, |pane, window, cx| {
21378            pane.close_inactive_items(&CloseInactiveItems::default(), window, cx)
21379        })
21380        .await
21381        .unwrap();
21382    drop(editor_2);
21383    pane_2.update(cx, |pane, cx| {
21384        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21385        open_editor.update(cx, |editor, cx| {
21386            assert_eq!(
21387                editor.display_text(cx),
21388                lib_text,
21389                "Other file should be open and active in another panel too",
21390            );
21391        });
21392        assert_eq!(
21393            pane.items().count(),
21394            1,
21395            "No other editors should be open in another pane",
21396        );
21397    });
21398
21399    let _editor_1_reopened = workspace
21400        .update_in(cx, |workspace, window, cx| {
21401            workspace.open_path(
21402                (worktree_id, "main.rs"),
21403                Some(pane_1.downgrade()),
21404                true,
21405                window,
21406                cx,
21407            )
21408        })
21409        .unwrap()
21410        .await
21411        .downcast::<Editor>()
21412        .unwrap();
21413    let _editor_2_reopened = workspace
21414        .update_in(cx, |workspace, window, cx| {
21415            workspace.open_path(
21416                (worktree_id, "main.rs"),
21417                Some(pane_2.downgrade()),
21418                true,
21419                window,
21420                cx,
21421            )
21422        })
21423        .unwrap()
21424        .await
21425        .downcast::<Editor>()
21426        .unwrap();
21427    pane_1.update(cx, |pane, cx| {
21428        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21429        open_editor.update(cx, |editor, cx| {
21430            assert_eq!(
21431                editor.display_text(cx),
21432                main_text,
21433                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
21434            );
21435            assert_eq!(
21436                editor
21437                    .selections
21438                    .all::<Point>(cx)
21439                    .into_iter()
21440                    .map(|s| s.range())
21441                    .collect::<Vec<_>>(),
21442                expected_ranges,
21443                "Previous editor in the 1st panel had selections and should get them restored on reopen",
21444            );
21445        })
21446    });
21447    pane_2.update(cx, |pane, cx| {
21448        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21449        open_editor.update(cx, |editor, cx| {
21450            assert_eq!(
21451                editor.display_text(cx),
21452                r#"fn main() {
21453⋯rintln!("1");
21454⋯intln!("2");
21455⋯ntln!("3");
21456println!("4");
21457println!("5");
21458}"#,
21459                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
21460            );
21461            assert_eq!(
21462                editor
21463                    .selections
21464                    .all::<Point>(cx)
21465                    .into_iter()
21466                    .map(|s| s.range())
21467                    .collect::<Vec<_>>(),
21468                vec![Point::zero()..Point::zero()],
21469                "Previous editor in the 2nd pane had no selections changed hence should restore none",
21470            );
21471        })
21472    });
21473}
21474
21475#[gpui::test]
21476async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
21477    init_test(cx, |_| {});
21478
21479    let fs = FakeFs::new(cx.executor());
21480    let main_text = r#"fn main() {
21481println!("1");
21482println!("2");
21483println!("3");
21484println!("4");
21485println!("5");
21486}"#;
21487    let lib_text = "mod foo {}";
21488    fs.insert_tree(
21489        path!("/a"),
21490        json!({
21491            "lib.rs": lib_text,
21492            "main.rs": main_text,
21493        }),
21494    )
21495    .await;
21496
21497    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21498    let (workspace, cx) =
21499        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21500    let worktree_id = workspace.update(cx, |workspace, cx| {
21501        workspace.project().update(cx, |project, cx| {
21502            project.worktrees(cx).next().unwrap().read(cx).id()
21503        })
21504    });
21505
21506    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21507    let editor = workspace
21508        .update_in(cx, |workspace, window, cx| {
21509            workspace.open_path(
21510                (worktree_id, "main.rs"),
21511                Some(pane.downgrade()),
21512                true,
21513                window,
21514                cx,
21515            )
21516        })
21517        .unwrap()
21518        .await
21519        .downcast::<Editor>()
21520        .unwrap();
21521    pane.update(cx, |pane, cx| {
21522        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21523        open_editor.update(cx, |editor, cx| {
21524            assert_eq!(
21525                editor.display_text(cx),
21526                main_text,
21527                "Original main.rs text on initial open",
21528            );
21529        })
21530    });
21531    editor.update_in(cx, |editor, window, cx| {
21532        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
21533    });
21534
21535    cx.update_global(|store: &mut SettingsStore, cx| {
21536        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21537            s.restore_on_file_reopen = Some(false);
21538        });
21539    });
21540    editor.update_in(cx, |editor, window, cx| {
21541        editor.fold_ranges(
21542            vec![
21543                Point::new(1, 0)..Point::new(1, 1),
21544                Point::new(2, 0)..Point::new(2, 2),
21545                Point::new(3, 0)..Point::new(3, 3),
21546            ],
21547            false,
21548            window,
21549            cx,
21550        );
21551    });
21552    pane.update_in(cx, |pane, window, cx| {
21553        pane.close_all_items(&CloseAllItems::default(), window, cx)
21554    })
21555    .await
21556    .unwrap();
21557    pane.update(cx, |pane, _| {
21558        assert!(pane.active_item().is_none());
21559    });
21560    cx.update_global(|store: &mut SettingsStore, cx| {
21561        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
21562            s.restore_on_file_reopen = Some(true);
21563        });
21564    });
21565
21566    let _editor_reopened = workspace
21567        .update_in(cx, |workspace, window, cx| {
21568            workspace.open_path(
21569                (worktree_id, "main.rs"),
21570                Some(pane.downgrade()),
21571                true,
21572                window,
21573                cx,
21574            )
21575        })
21576        .unwrap()
21577        .await
21578        .downcast::<Editor>()
21579        .unwrap();
21580    pane.update(cx, |pane, cx| {
21581        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21582        open_editor.update(cx, |editor, cx| {
21583            assert_eq!(
21584                editor.display_text(cx),
21585                main_text,
21586                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
21587            );
21588        })
21589    });
21590}
21591
21592#[gpui::test]
21593async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
21594    struct EmptyModalView {
21595        focus_handle: gpui::FocusHandle,
21596    }
21597    impl EventEmitter<DismissEvent> for EmptyModalView {}
21598    impl Render for EmptyModalView {
21599        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
21600            div()
21601        }
21602    }
21603    impl Focusable for EmptyModalView {
21604        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
21605            self.focus_handle.clone()
21606        }
21607    }
21608    impl workspace::ModalView for EmptyModalView {}
21609    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
21610        EmptyModalView {
21611            focus_handle: cx.focus_handle(),
21612        }
21613    }
21614
21615    init_test(cx, |_| {});
21616
21617    let fs = FakeFs::new(cx.executor());
21618    let project = Project::test(fs, [], cx).await;
21619    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21620    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
21621    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21622    let editor = cx.new_window_entity(|window, cx| {
21623        Editor::new(
21624            EditorMode::full(),
21625            buffer,
21626            Some(project.clone()),
21627            window,
21628            cx,
21629        )
21630    });
21631    workspace
21632        .update(cx, |workspace, window, cx| {
21633            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
21634        })
21635        .unwrap();
21636    editor.update_in(cx, |editor, window, cx| {
21637        editor.open_context_menu(&OpenContextMenu, window, cx);
21638        assert!(editor.mouse_context_menu.is_some());
21639    });
21640    workspace
21641        .update(cx, |workspace, window, cx| {
21642            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
21643        })
21644        .unwrap();
21645    cx.read(|cx| {
21646        assert!(editor.read(cx).mouse_context_menu.is_none());
21647    });
21648}
21649
21650#[gpui::test]
21651async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
21652    init_test(cx, |_| {});
21653
21654    let fs = FakeFs::new(cx.executor());
21655    fs.insert_file(path!("/file.html"), Default::default())
21656        .await;
21657
21658    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21659
21660    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21661    let html_language = Arc::new(Language::new(
21662        LanguageConfig {
21663            name: "HTML".into(),
21664            matcher: LanguageMatcher {
21665                path_suffixes: vec!["html".to_string()],
21666                ..LanguageMatcher::default()
21667            },
21668            brackets: BracketPairConfig {
21669                pairs: vec![BracketPair {
21670                    start: "<".into(),
21671                    end: ">".into(),
21672                    close: true,
21673                    ..Default::default()
21674                }],
21675                ..Default::default()
21676            },
21677            ..Default::default()
21678        },
21679        Some(tree_sitter_html::LANGUAGE.into()),
21680    ));
21681    language_registry.add(html_language);
21682    let mut fake_servers = language_registry.register_fake_lsp(
21683        "HTML",
21684        FakeLspAdapter {
21685            capabilities: lsp::ServerCapabilities {
21686                completion_provider: Some(lsp::CompletionOptions {
21687                    resolve_provider: Some(true),
21688                    ..Default::default()
21689                }),
21690                ..Default::default()
21691            },
21692            ..Default::default()
21693        },
21694    );
21695
21696    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21697    let cx = &mut VisualTestContext::from_window(*workspace, cx);
21698
21699    let worktree_id = workspace
21700        .update(cx, |workspace, _window, cx| {
21701            workspace.project().update(cx, |project, cx| {
21702                project.worktrees(cx).next().unwrap().read(cx).id()
21703            })
21704        })
21705        .unwrap();
21706    project
21707        .update(cx, |project, cx| {
21708            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
21709        })
21710        .await
21711        .unwrap();
21712    let editor = workspace
21713        .update(cx, |workspace, window, cx| {
21714            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
21715        })
21716        .unwrap()
21717        .await
21718        .unwrap()
21719        .downcast::<Editor>()
21720        .unwrap();
21721
21722    let fake_server = fake_servers.next().await.unwrap();
21723    editor.update_in(cx, |editor, window, cx| {
21724        editor.set_text("<ad></ad>", window, cx);
21725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21726            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
21727        });
21728        let Some((buffer, _)) = editor
21729            .buffer
21730            .read(cx)
21731            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
21732        else {
21733            panic!("Failed to get buffer for selection position");
21734        };
21735        let buffer = buffer.read(cx);
21736        let buffer_id = buffer.remote_id();
21737        let opening_range =
21738            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
21739        let closing_range =
21740            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
21741        let mut linked_ranges = HashMap::default();
21742        linked_ranges.insert(
21743            buffer_id,
21744            vec![(opening_range.clone(), vec![closing_range.clone()])],
21745        );
21746        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
21747    });
21748    let mut completion_handle =
21749        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
21750            Ok(Some(lsp::CompletionResponse::Array(vec![
21751                lsp::CompletionItem {
21752                    label: "head".to_string(),
21753                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21754                        lsp::InsertReplaceEdit {
21755                            new_text: "head".to_string(),
21756                            insert: lsp::Range::new(
21757                                lsp::Position::new(0, 1),
21758                                lsp::Position::new(0, 3),
21759                            ),
21760                            replace: lsp::Range::new(
21761                                lsp::Position::new(0, 1),
21762                                lsp::Position::new(0, 3),
21763                            ),
21764                        },
21765                    )),
21766                    ..Default::default()
21767                },
21768            ])))
21769        });
21770    editor.update_in(cx, |editor, window, cx| {
21771        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
21772    });
21773    cx.run_until_parked();
21774    completion_handle.next().await.unwrap();
21775    editor.update(cx, |editor, _| {
21776        assert!(
21777            editor.context_menu_visible(),
21778            "Completion menu should be visible"
21779        );
21780    });
21781    editor.update_in(cx, |editor, window, cx| {
21782        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
21783    });
21784    cx.executor().run_until_parked();
21785    editor.update(cx, |editor, cx| {
21786        assert_eq!(editor.text(cx), "<head></head>");
21787    });
21788}
21789
21790#[gpui::test]
21791async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
21792    init_test(cx, |_| {});
21793
21794    let fs = FakeFs::new(cx.executor());
21795    fs.insert_tree(
21796        path!("/root"),
21797        json!({
21798            "a": {
21799                "main.rs": "fn main() {}",
21800            },
21801            "foo": {
21802                "bar": {
21803                    "external_file.rs": "pub mod external {}",
21804                }
21805            }
21806        }),
21807    )
21808    .await;
21809
21810    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
21811    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21812    language_registry.add(rust_lang());
21813    let _fake_servers = language_registry.register_fake_lsp(
21814        "Rust",
21815        FakeLspAdapter {
21816            ..FakeLspAdapter::default()
21817        },
21818    );
21819    let (workspace, cx) =
21820        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21821    let worktree_id = workspace.update(cx, |workspace, cx| {
21822        workspace.project().update(cx, |project, cx| {
21823            project.worktrees(cx).next().unwrap().read(cx).id()
21824        })
21825    });
21826
21827    let assert_language_servers_count =
21828        |expected: usize, context: &str, cx: &mut VisualTestContext| {
21829            project.update(cx, |project, cx| {
21830                let current = project
21831                    .lsp_store()
21832                    .read(cx)
21833                    .as_local()
21834                    .unwrap()
21835                    .language_servers
21836                    .len();
21837                assert_eq!(expected, current, "{context}");
21838            });
21839        };
21840
21841    assert_language_servers_count(
21842        0,
21843        "No servers should be running before any file is open",
21844        cx,
21845    );
21846    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
21847    let main_editor = workspace
21848        .update_in(cx, |workspace, window, cx| {
21849            workspace.open_path(
21850                (worktree_id, "main.rs"),
21851                Some(pane.downgrade()),
21852                true,
21853                window,
21854                cx,
21855            )
21856        })
21857        .unwrap()
21858        .await
21859        .downcast::<Editor>()
21860        .unwrap();
21861    pane.update(cx, |pane, cx| {
21862        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21863        open_editor.update(cx, |editor, cx| {
21864            assert_eq!(
21865                editor.display_text(cx),
21866                "fn main() {}",
21867                "Original main.rs text on initial open",
21868            );
21869        });
21870        assert_eq!(open_editor, main_editor);
21871    });
21872    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
21873
21874    let external_editor = workspace
21875        .update_in(cx, |workspace, window, cx| {
21876            workspace.open_abs_path(
21877                PathBuf::from("/root/foo/bar/external_file.rs"),
21878                OpenOptions::default(),
21879                window,
21880                cx,
21881            )
21882        })
21883        .await
21884        .expect("opening external file")
21885        .downcast::<Editor>()
21886        .expect("downcasted external file's open element to editor");
21887    pane.update(cx, |pane, cx| {
21888        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21889        open_editor.update(cx, |editor, cx| {
21890            assert_eq!(
21891                editor.display_text(cx),
21892                "pub mod external {}",
21893                "External file is open now",
21894            );
21895        });
21896        assert_eq!(open_editor, external_editor);
21897    });
21898    assert_language_servers_count(
21899        1,
21900        "Second, external, *.rs file should join the existing server",
21901        cx,
21902    );
21903
21904    pane.update_in(cx, |pane, window, cx| {
21905        pane.close_active_item(&CloseActiveItem::default(), window, cx)
21906    })
21907    .await
21908    .unwrap();
21909    pane.update_in(cx, |pane, window, cx| {
21910        pane.navigate_backward(window, cx);
21911    });
21912    cx.run_until_parked();
21913    pane.update(cx, |pane, cx| {
21914        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
21915        open_editor.update(cx, |editor, cx| {
21916            assert_eq!(
21917                editor.display_text(cx),
21918                "pub mod external {}",
21919                "External file is open now",
21920            );
21921        });
21922    });
21923    assert_language_servers_count(
21924        1,
21925        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
21926        cx,
21927    );
21928
21929    cx.update(|_, cx| {
21930        workspace::reload(&workspace::Reload::default(), cx);
21931    });
21932    assert_language_servers_count(
21933        1,
21934        "After reloading the worktree with local and external files opened, only one project should be started",
21935        cx,
21936    );
21937}
21938
21939#[gpui::test]
21940async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
21941    init_test(cx, |_| {});
21942
21943    let mut cx = EditorTestContext::new(cx).await;
21944    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
21945    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21946
21947    // test cursor move to start of each line on tab
21948    // for `if`, `elif`, `else`, `while`, `with` and `for`
21949    cx.set_state(indoc! {"
21950        def main():
21951        ˇ    for item in items:
21952        ˇ        while item.active:
21953        ˇ            if item.value > 10:
21954        ˇ                continue
21955        ˇ            elif item.value < 0:
21956        ˇ                break
21957        ˇ            else:
21958        ˇ                with item.context() as ctx:
21959        ˇ                    yield count
21960        ˇ        else:
21961        ˇ            log('while else')
21962        ˇ    else:
21963        ˇ        log('for else')
21964    "});
21965    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21966    cx.assert_editor_state(indoc! {"
21967        def main():
21968            ˇfor item in items:
21969                ˇwhile item.active:
21970                    ˇif item.value > 10:
21971                        ˇcontinue
21972                    ˇelif item.value < 0:
21973                        ˇbreak
21974                    ˇelse:
21975                        ˇwith item.context() as ctx:
21976                            ˇyield count
21977                ˇelse:
21978                    ˇlog('while else')
21979            ˇelse:
21980                ˇlog('for else')
21981    "});
21982    // test relative indent is preserved when tab
21983    // for `if`, `elif`, `else`, `while`, `with` and `for`
21984    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
21985    cx.assert_editor_state(indoc! {"
21986        def main():
21987                ˇfor item in items:
21988                    ˇwhile item.active:
21989                        ˇif item.value > 10:
21990                            ˇcontinue
21991                        ˇelif item.value < 0:
21992                            ˇbreak
21993                        ˇelse:
21994                            ˇwith item.context() as ctx:
21995                                ˇyield count
21996                    ˇelse:
21997                        ˇlog('while else')
21998                ˇelse:
21999                    ˇlog('for else')
22000    "});
22001
22002    // test cursor move to start of each line on tab
22003    // for `try`, `except`, `else`, `finally`, `match` and `def`
22004    cx.set_state(indoc! {"
22005        def main():
22006        ˇ    try:
22007        ˇ        fetch()
22008        ˇ    except ValueError:
22009        ˇ        handle_error()
22010        ˇ    else:
22011        ˇ        match value:
22012        ˇ            case _:
22013        ˇ    finally:
22014        ˇ        def status():
22015        ˇ            return 0
22016    "});
22017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22018    cx.assert_editor_state(indoc! {"
22019        def main():
22020            ˇtry:
22021                ˇfetch()
22022            ˇexcept ValueError:
22023                ˇhandle_error()
22024            ˇelse:
22025                ˇmatch value:
22026                    ˇcase _:
22027            ˇfinally:
22028                ˇdef status():
22029                    ˇreturn 0
22030    "});
22031    // test relative indent is preserved when tab
22032    // for `try`, `except`, `else`, `finally`, `match` and `def`
22033    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22034    cx.assert_editor_state(indoc! {"
22035        def main():
22036                ˇtry:
22037                    ˇfetch()
22038                ˇexcept ValueError:
22039                    ˇhandle_error()
22040                ˇelse:
22041                    ˇmatch value:
22042                        ˇcase _:
22043                ˇfinally:
22044                    ˇdef status():
22045                        ˇreturn 0
22046    "});
22047}
22048
22049#[gpui::test]
22050async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22051    init_test(cx, |_| {});
22052
22053    let mut cx = EditorTestContext::new(cx).await;
22054    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22055    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22056
22057    // test `else` auto outdents when typed inside `if` block
22058    cx.set_state(indoc! {"
22059        def main():
22060            if i == 2:
22061                return
22062                ˇ
22063    "});
22064    cx.update_editor(|editor, window, cx| {
22065        editor.handle_input("else:", window, cx);
22066    });
22067    cx.assert_editor_state(indoc! {"
22068        def main():
22069            if i == 2:
22070                return
22071            else:ˇ
22072    "});
22073
22074    // test `except` auto outdents when typed inside `try` block
22075    cx.set_state(indoc! {"
22076        def main():
22077            try:
22078                i = 2
22079                ˇ
22080    "});
22081    cx.update_editor(|editor, window, cx| {
22082        editor.handle_input("except:", window, cx);
22083    });
22084    cx.assert_editor_state(indoc! {"
22085        def main():
22086            try:
22087                i = 2
22088            except:ˇ
22089    "});
22090
22091    // test `else` auto outdents when typed inside `except` block
22092    cx.set_state(indoc! {"
22093        def main():
22094            try:
22095                i = 2
22096            except:
22097                j = 2
22098                ˇ
22099    "});
22100    cx.update_editor(|editor, window, cx| {
22101        editor.handle_input("else:", window, cx);
22102    });
22103    cx.assert_editor_state(indoc! {"
22104        def main():
22105            try:
22106                i = 2
22107            except:
22108                j = 2
22109            else:ˇ
22110    "});
22111
22112    // test `finally` auto outdents when typed inside `else` block
22113    cx.set_state(indoc! {"
22114        def main():
22115            try:
22116                i = 2
22117            except:
22118                j = 2
22119            else:
22120                k = 2
22121                ˇ
22122    "});
22123    cx.update_editor(|editor, window, cx| {
22124        editor.handle_input("finally:", window, cx);
22125    });
22126    cx.assert_editor_state(indoc! {"
22127        def main():
22128            try:
22129                i = 2
22130            except:
22131                j = 2
22132            else:
22133                k = 2
22134            finally:ˇ
22135    "});
22136
22137    // test `else` does not outdents when typed inside `except` block right after for block
22138    cx.set_state(indoc! {"
22139        def main():
22140            try:
22141                i = 2
22142            except:
22143                for i in range(n):
22144                    pass
22145                ˇ
22146    "});
22147    cx.update_editor(|editor, window, cx| {
22148        editor.handle_input("else:", window, cx);
22149    });
22150    cx.assert_editor_state(indoc! {"
22151        def main():
22152            try:
22153                i = 2
22154            except:
22155                for i in range(n):
22156                    pass
22157                else:ˇ
22158    "});
22159
22160    // test `finally` auto outdents when typed inside `else` block right after for block
22161    cx.set_state(indoc! {"
22162        def main():
22163            try:
22164                i = 2
22165            except:
22166                j = 2
22167            else:
22168                for i in range(n):
22169                    pass
22170                ˇ
22171    "});
22172    cx.update_editor(|editor, window, cx| {
22173        editor.handle_input("finally:", window, cx);
22174    });
22175    cx.assert_editor_state(indoc! {"
22176        def main():
22177            try:
22178                i = 2
22179            except:
22180                j = 2
22181            else:
22182                for i in range(n):
22183                    pass
22184            finally:ˇ
22185    "});
22186
22187    // test `except` outdents to inner "try" block
22188    cx.set_state(indoc! {"
22189        def main():
22190            try:
22191                i = 2
22192                if i == 2:
22193                    try:
22194                        i = 3
22195                        ˇ
22196    "});
22197    cx.update_editor(|editor, window, cx| {
22198        editor.handle_input("except:", window, cx);
22199    });
22200    cx.assert_editor_state(indoc! {"
22201        def main():
22202            try:
22203                i = 2
22204                if i == 2:
22205                    try:
22206                        i = 3
22207                    except:ˇ
22208    "});
22209
22210    // test `except` outdents to outer "try" block
22211    cx.set_state(indoc! {"
22212        def main():
22213            try:
22214                i = 2
22215                if i == 2:
22216                    try:
22217                        i = 3
22218                ˇ
22219    "});
22220    cx.update_editor(|editor, window, cx| {
22221        editor.handle_input("except:", window, cx);
22222    });
22223    cx.assert_editor_state(indoc! {"
22224        def main():
22225            try:
22226                i = 2
22227                if i == 2:
22228                    try:
22229                        i = 3
22230            except:ˇ
22231    "});
22232
22233    // test `else` stays at correct indent when typed after `for` block
22234    cx.set_state(indoc! {"
22235        def main():
22236            for i in range(10):
22237                if i == 3:
22238                    break
22239            ˇ
22240    "});
22241    cx.update_editor(|editor, window, cx| {
22242        editor.handle_input("else:", window, cx);
22243    });
22244    cx.assert_editor_state(indoc! {"
22245        def main():
22246            for i in range(10):
22247                if i == 3:
22248                    break
22249            else:ˇ
22250    "});
22251
22252    // test does not outdent on typing after line with square brackets
22253    cx.set_state(indoc! {"
22254        def f() -> list[str]:
22255            ˇ
22256    "});
22257    cx.update_editor(|editor, window, cx| {
22258        editor.handle_input("a", window, cx);
22259    });
22260    cx.assert_editor_state(indoc! {"
22261        def f() -> list[str]:
2226222263    "});
22264}
22265
22266#[gpui::test]
22267async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
22268    init_test(cx, |_| {});
22269    update_test_language_settings(cx, |settings| {
22270        settings.defaults.extend_comment_on_newline = Some(false);
22271    });
22272    let mut cx = EditorTestContext::new(cx).await;
22273    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22274    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22275
22276    // test correct indent after newline on comment
22277    cx.set_state(indoc! {"
22278        # COMMENT:ˇ
22279    "});
22280    cx.update_editor(|editor, window, cx| {
22281        editor.newline(&Newline, window, cx);
22282    });
22283    cx.assert_editor_state(indoc! {"
22284        # COMMENT:
22285        ˇ
22286    "});
22287
22288    // test correct indent after newline in brackets
22289    cx.set_state(indoc! {"
22290        {ˇ}
22291    "});
22292    cx.update_editor(|editor, window, cx| {
22293        editor.newline(&Newline, window, cx);
22294    });
22295    cx.run_until_parked();
22296    cx.assert_editor_state(indoc! {"
22297        {
22298            ˇ
22299        }
22300    "});
22301
22302    cx.set_state(indoc! {"
22303        (ˇ)
22304    "});
22305    cx.update_editor(|editor, window, cx| {
22306        editor.newline(&Newline, window, cx);
22307    });
22308    cx.run_until_parked();
22309    cx.assert_editor_state(indoc! {"
22310        (
22311            ˇ
22312        )
22313    "});
22314
22315    // do not indent after empty lists or dictionaries
22316    cx.set_state(indoc! {"
22317        a = []ˇ
22318    "});
22319    cx.update_editor(|editor, window, cx| {
22320        editor.newline(&Newline, window, cx);
22321    });
22322    cx.run_until_parked();
22323    cx.assert_editor_state(indoc! {"
22324        a = []
22325        ˇ
22326    "});
22327}
22328
22329fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
22330    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
22331    point..point
22332}
22333
22334#[track_caller]
22335fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
22336    let (text, ranges) = marked_text_ranges(marked_text, true);
22337    assert_eq!(editor.text(cx), text);
22338    assert_eq!(
22339        editor.selections.ranges(cx),
22340        ranges,
22341        "Assert selections are {}",
22342        marked_text
22343    );
22344}
22345
22346pub fn handle_signature_help_request(
22347    cx: &mut EditorLspTestContext,
22348    mocked_response: lsp::SignatureHelp,
22349) -> impl Future<Output = ()> + use<> {
22350    let mut request =
22351        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
22352            let mocked_response = mocked_response.clone();
22353            async move { Ok(Some(mocked_response)) }
22354        });
22355
22356    async move {
22357        request.next().await;
22358    }
22359}
22360
22361#[track_caller]
22362pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
22363    cx.update_editor(|editor, _, _| {
22364        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
22365            let entries = menu.entries.borrow();
22366            let entries = entries
22367                .iter()
22368                .map(|entry| entry.string.as_str())
22369                .collect::<Vec<_>>();
22370            assert_eq!(entries, expected);
22371        } else {
22372            panic!("Expected completions menu");
22373        }
22374    });
22375}
22376
22377/// Handle completion request passing a marked string specifying where the completion
22378/// should be triggered from using '|' character, what range should be replaced, and what completions
22379/// should be returned using '<' and '>' to delimit the range.
22380///
22381/// Also see `handle_completion_request_with_insert_and_replace`.
22382#[track_caller]
22383pub fn handle_completion_request(
22384    marked_string: &str,
22385    completions: Vec<&'static str>,
22386    is_incomplete: bool,
22387    counter: Arc<AtomicUsize>,
22388    cx: &mut EditorLspTestContext,
22389) -> impl Future<Output = ()> {
22390    let complete_from_marker: TextRangeMarker = '|'.into();
22391    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22392    let (_, mut marked_ranges) = marked_text_ranges_by(
22393        marked_string,
22394        vec![complete_from_marker.clone(), replace_range_marker.clone()],
22395    );
22396
22397    let complete_from_position =
22398        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22399    let replace_range =
22400        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22401
22402    let mut request =
22403        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22404            let completions = completions.clone();
22405            counter.fetch_add(1, atomic::Ordering::Release);
22406            async move {
22407                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22408                assert_eq!(
22409                    params.text_document_position.position,
22410                    complete_from_position
22411                );
22412                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
22413                    is_incomplete: is_incomplete,
22414                    item_defaults: None,
22415                    items: completions
22416                        .iter()
22417                        .map(|completion_text| lsp::CompletionItem {
22418                            label: completion_text.to_string(),
22419                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
22420                                range: replace_range,
22421                                new_text: completion_text.to_string(),
22422                            })),
22423                            ..Default::default()
22424                        })
22425                        .collect(),
22426                })))
22427            }
22428        });
22429
22430    async move {
22431        request.next().await;
22432    }
22433}
22434
22435/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
22436/// given instead, which also contains an `insert` range.
22437///
22438/// This function uses markers to define ranges:
22439/// - `|` marks the cursor position
22440/// - `<>` marks the replace range
22441/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
22442pub fn handle_completion_request_with_insert_and_replace(
22443    cx: &mut EditorLspTestContext,
22444    marked_string: &str,
22445    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
22446    counter: Arc<AtomicUsize>,
22447) -> impl Future<Output = ()> {
22448    let complete_from_marker: TextRangeMarker = '|'.into();
22449    let replace_range_marker: TextRangeMarker = ('<', '>').into();
22450    let insert_range_marker: TextRangeMarker = ('{', '}').into();
22451
22452    let (_, mut marked_ranges) = marked_text_ranges_by(
22453        marked_string,
22454        vec![
22455            complete_from_marker.clone(),
22456            replace_range_marker.clone(),
22457            insert_range_marker.clone(),
22458        ],
22459    );
22460
22461    let complete_from_position =
22462        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
22463    let replace_range =
22464        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
22465
22466    let insert_range = match marked_ranges.remove(&insert_range_marker) {
22467        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
22468        _ => lsp::Range {
22469            start: replace_range.start,
22470            end: complete_from_position,
22471        },
22472    };
22473
22474    let mut request =
22475        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
22476            let completions = completions.clone();
22477            counter.fetch_add(1, atomic::Ordering::Release);
22478            async move {
22479                assert_eq!(params.text_document_position.text_document.uri, url.clone());
22480                assert_eq!(
22481                    params.text_document_position.position, complete_from_position,
22482                    "marker `|` position doesn't match",
22483                );
22484                Ok(Some(lsp::CompletionResponse::Array(
22485                    completions
22486                        .iter()
22487                        .map(|(label, new_text)| lsp::CompletionItem {
22488                            label: label.to_string(),
22489                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22490                                lsp::InsertReplaceEdit {
22491                                    insert: insert_range,
22492                                    replace: replace_range,
22493                                    new_text: new_text.to_string(),
22494                                },
22495                            )),
22496                            ..Default::default()
22497                        })
22498                        .collect(),
22499                )))
22500            }
22501        });
22502
22503    async move {
22504        request.next().await;
22505    }
22506}
22507
22508fn handle_resolve_completion_request(
22509    cx: &mut EditorLspTestContext,
22510    edits: Option<Vec<(&'static str, &'static str)>>,
22511) -> impl Future<Output = ()> {
22512    let edits = edits.map(|edits| {
22513        edits
22514            .iter()
22515            .map(|(marked_string, new_text)| {
22516                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
22517                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
22518                lsp::TextEdit::new(replace_range, new_text.to_string())
22519            })
22520            .collect::<Vec<_>>()
22521    });
22522
22523    let mut request =
22524        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
22525            let edits = edits.clone();
22526            async move {
22527                Ok(lsp::CompletionItem {
22528                    additional_text_edits: edits,
22529                    ..Default::default()
22530                })
22531            }
22532        });
22533
22534    async move {
22535        request.next().await;
22536    }
22537}
22538
22539pub(crate) fn update_test_language_settings(
22540    cx: &mut TestAppContext,
22541    f: impl Fn(&mut AllLanguageSettingsContent),
22542) {
22543    cx.update(|cx| {
22544        SettingsStore::update_global(cx, |store, cx| {
22545            store.update_user_settings::<AllLanguageSettings>(cx, f);
22546        });
22547    });
22548}
22549
22550pub(crate) fn update_test_project_settings(
22551    cx: &mut TestAppContext,
22552    f: impl Fn(&mut ProjectSettings),
22553) {
22554    cx.update(|cx| {
22555        SettingsStore::update_global(cx, |store, cx| {
22556            store.update_user_settings::<ProjectSettings>(cx, f);
22557        });
22558    });
22559}
22560
22561pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
22562    cx.update(|cx| {
22563        assets::Assets.load_test_fonts(cx);
22564        let store = SettingsStore::test(cx);
22565        cx.set_global(store);
22566        theme::init(theme::LoadThemes::JustBase, cx);
22567        release_channel::init(SemanticVersion::default(), cx);
22568        client::init_settings(cx);
22569        language::init(cx);
22570        Project::init_settings(cx);
22571        workspace::init_settings(cx);
22572        crate::init(cx);
22573    });
22574
22575    update_test_language_settings(cx, f);
22576}
22577
22578#[track_caller]
22579fn assert_hunk_revert(
22580    not_reverted_text_with_selections: &str,
22581    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
22582    expected_reverted_text_with_selections: &str,
22583    base_text: &str,
22584    cx: &mut EditorLspTestContext,
22585) {
22586    cx.set_state(not_reverted_text_with_selections);
22587    cx.set_head_text(base_text);
22588    cx.executor().run_until_parked();
22589
22590    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
22591        let snapshot = editor.snapshot(window, cx);
22592        let reverted_hunk_statuses = snapshot
22593            .buffer_snapshot
22594            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
22595            .map(|hunk| hunk.status().kind)
22596            .collect::<Vec<_>>();
22597
22598        editor.git_restore(&Default::default(), window, cx);
22599        reverted_hunk_statuses
22600    });
22601    cx.executor().run_until_parked();
22602    cx.assert_editor_state(expected_reverted_text_with_selections);
22603    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
22604}
22605
22606#[gpui::test(iterations = 10)]
22607async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
22608    init_test(cx, |_| {});
22609
22610    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
22611    let counter = diagnostic_requests.clone();
22612
22613    let fs = FakeFs::new(cx.executor());
22614    fs.insert_tree(
22615        path!("/a"),
22616        json!({
22617            "first.rs": "fn main() { let a = 5; }",
22618            "second.rs": "// Test file",
22619        }),
22620    )
22621    .await;
22622
22623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22625    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22626
22627    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22628    language_registry.add(rust_lang());
22629    let mut fake_servers = language_registry.register_fake_lsp(
22630        "Rust",
22631        FakeLspAdapter {
22632            capabilities: lsp::ServerCapabilities {
22633                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
22634                    lsp::DiagnosticOptions {
22635                        identifier: None,
22636                        inter_file_dependencies: true,
22637                        workspace_diagnostics: true,
22638                        work_done_progress_options: Default::default(),
22639                    },
22640                )),
22641                ..Default::default()
22642            },
22643            ..Default::default()
22644        },
22645    );
22646
22647    let editor = workspace
22648        .update(cx, |workspace, window, cx| {
22649            workspace.open_abs_path(
22650                PathBuf::from(path!("/a/first.rs")),
22651                OpenOptions::default(),
22652                window,
22653                cx,
22654            )
22655        })
22656        .unwrap()
22657        .await
22658        .unwrap()
22659        .downcast::<Editor>()
22660        .unwrap();
22661    let fake_server = fake_servers.next().await.unwrap();
22662    let server_id = fake_server.server.server_id();
22663    let mut first_request = fake_server
22664        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
22665            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
22666            let result_id = Some(new_result_id.to_string());
22667            assert_eq!(
22668                params.text_document.uri,
22669                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22670            );
22671            async move {
22672                Ok(lsp::DocumentDiagnosticReportResult::Report(
22673                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
22674                        related_documents: None,
22675                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
22676                            items: Vec::new(),
22677                            result_id,
22678                        },
22679                    }),
22680                ))
22681            }
22682        });
22683
22684    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
22685        project.update(cx, |project, cx| {
22686            let buffer_id = editor
22687                .read(cx)
22688                .buffer()
22689                .read(cx)
22690                .as_singleton()
22691                .expect("created a singleton buffer")
22692                .read(cx)
22693                .remote_id();
22694            let buffer_result_id = project
22695                .lsp_store()
22696                .read(cx)
22697                .result_id(server_id, buffer_id, cx);
22698            assert_eq!(expected, buffer_result_id);
22699        });
22700    };
22701
22702    ensure_result_id(None, cx);
22703    cx.executor().advance_clock(Duration::from_millis(60));
22704    cx.executor().run_until_parked();
22705    assert_eq!(
22706        diagnostic_requests.load(atomic::Ordering::Acquire),
22707        1,
22708        "Opening file should trigger diagnostic request"
22709    );
22710    first_request
22711        .next()
22712        .await
22713        .expect("should have sent the first diagnostics pull request");
22714    ensure_result_id(Some("1".to_string()), cx);
22715
22716    // Editing should trigger diagnostics
22717    editor.update_in(cx, |editor, window, cx| {
22718        editor.handle_input("2", window, cx)
22719    });
22720    cx.executor().advance_clock(Duration::from_millis(60));
22721    cx.executor().run_until_parked();
22722    assert_eq!(
22723        diagnostic_requests.load(atomic::Ordering::Acquire),
22724        2,
22725        "Editing should trigger diagnostic request"
22726    );
22727    ensure_result_id(Some("2".to_string()), cx);
22728
22729    // Moving cursor should not trigger diagnostic request
22730    editor.update_in(cx, |editor, window, cx| {
22731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22732            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
22733        });
22734    });
22735    cx.executor().advance_clock(Duration::from_millis(60));
22736    cx.executor().run_until_parked();
22737    assert_eq!(
22738        diagnostic_requests.load(atomic::Ordering::Acquire),
22739        2,
22740        "Cursor movement should not trigger diagnostic request"
22741    );
22742    ensure_result_id(Some("2".to_string()), cx);
22743    // Multiple rapid edits should be debounced
22744    for _ in 0..5 {
22745        editor.update_in(cx, |editor, window, cx| {
22746            editor.handle_input("x", window, cx)
22747        });
22748    }
22749    cx.executor().advance_clock(Duration::from_millis(60));
22750    cx.executor().run_until_parked();
22751
22752    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
22753    assert!(
22754        final_requests <= 4,
22755        "Multiple rapid edits should be debounced (got {final_requests} requests)",
22756    );
22757    ensure_result_id(Some(final_requests.to_string()), cx);
22758}
22759
22760#[gpui::test]
22761async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
22762    // Regression test for issue #11671
22763    // Previously, adding a cursor after moving multiple cursors would reset
22764    // the cursor count instead of adding to the existing cursors.
22765    init_test(cx, |_| {});
22766    let mut cx = EditorTestContext::new(cx).await;
22767
22768    // Create a simple buffer with cursor at start
22769    cx.set_state(indoc! {"
22770        ˇaaaa
22771        bbbb
22772        cccc
22773        dddd
22774        eeee
22775        ffff
22776        gggg
22777        hhhh"});
22778
22779    // Add 2 cursors below (so we have 3 total)
22780    cx.update_editor(|editor, window, cx| {
22781        editor.add_selection_below(&Default::default(), window, cx);
22782        editor.add_selection_below(&Default::default(), window, cx);
22783    });
22784
22785    // Verify we have 3 cursors
22786    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
22787    assert_eq!(
22788        initial_count, 3,
22789        "Should have 3 cursors after adding 2 below"
22790    );
22791
22792    // Move down one line
22793    cx.update_editor(|editor, window, cx| {
22794        editor.move_down(&MoveDown, window, cx);
22795    });
22796
22797    // Add another cursor below
22798    cx.update_editor(|editor, window, cx| {
22799        editor.add_selection_below(&Default::default(), window, cx);
22800    });
22801
22802    // Should now have 4 cursors (3 original + 1 new)
22803    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
22804    assert_eq!(
22805        final_count, 4,
22806        "Should have 4 cursors after moving and adding another"
22807    );
22808}
22809
22810#[gpui::test(iterations = 10)]
22811async fn test_document_colors(cx: &mut TestAppContext) {
22812    let expected_color = Rgba {
22813        r: 0.33,
22814        g: 0.33,
22815        b: 0.33,
22816        a: 0.33,
22817    };
22818
22819    init_test(cx, |_| {});
22820
22821    let fs = FakeFs::new(cx.executor());
22822    fs.insert_tree(
22823        path!("/a"),
22824        json!({
22825            "first.rs": "fn main() { let a = 5; }",
22826        }),
22827    )
22828    .await;
22829
22830    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22831    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22832    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22833
22834    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22835    language_registry.add(rust_lang());
22836    let mut fake_servers = language_registry.register_fake_lsp(
22837        "Rust",
22838        FakeLspAdapter {
22839            capabilities: lsp::ServerCapabilities {
22840                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
22841                ..lsp::ServerCapabilities::default()
22842            },
22843            name: "rust-analyzer",
22844            ..FakeLspAdapter::default()
22845        },
22846    );
22847    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
22848        "Rust",
22849        FakeLspAdapter {
22850            capabilities: lsp::ServerCapabilities {
22851                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
22852                ..lsp::ServerCapabilities::default()
22853            },
22854            name: "not-rust-analyzer",
22855            ..FakeLspAdapter::default()
22856        },
22857    );
22858
22859    let editor = workspace
22860        .update(cx, |workspace, window, cx| {
22861            workspace.open_abs_path(
22862                PathBuf::from(path!("/a/first.rs")),
22863                OpenOptions::default(),
22864                window,
22865                cx,
22866            )
22867        })
22868        .unwrap()
22869        .await
22870        .unwrap()
22871        .downcast::<Editor>()
22872        .unwrap();
22873    let fake_language_server = fake_servers.next().await.unwrap();
22874    let fake_language_server_without_capabilities =
22875        fake_servers_without_capabilities.next().await.unwrap();
22876    let requests_made = Arc::new(AtomicUsize::new(0));
22877    let closure_requests_made = Arc::clone(&requests_made);
22878    let mut color_request_handle = fake_language_server
22879        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
22880            let requests_made = Arc::clone(&closure_requests_made);
22881            async move {
22882                assert_eq!(
22883                    params.text_document.uri,
22884                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
22885                );
22886                requests_made.fetch_add(1, atomic::Ordering::Release);
22887                Ok(vec![
22888                    lsp::ColorInformation {
22889                        range: lsp::Range {
22890                            start: lsp::Position {
22891                                line: 0,
22892                                character: 0,
22893                            },
22894                            end: lsp::Position {
22895                                line: 0,
22896                                character: 1,
22897                            },
22898                        },
22899                        color: lsp::Color {
22900                            red: 0.33,
22901                            green: 0.33,
22902                            blue: 0.33,
22903                            alpha: 0.33,
22904                        },
22905                    },
22906                    lsp::ColorInformation {
22907                        range: lsp::Range {
22908                            start: lsp::Position {
22909                                line: 0,
22910                                character: 0,
22911                            },
22912                            end: lsp::Position {
22913                                line: 0,
22914                                character: 1,
22915                            },
22916                        },
22917                        color: lsp::Color {
22918                            red: 0.33,
22919                            green: 0.33,
22920                            blue: 0.33,
22921                            alpha: 0.33,
22922                        },
22923                    },
22924                ])
22925            }
22926        });
22927
22928    let _handle = fake_language_server_without_capabilities
22929        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
22930            panic!("Should not be called");
22931        });
22932    cx.executor().advance_clock(Duration::from_millis(100));
22933    color_request_handle.next().await.unwrap();
22934    cx.run_until_parked();
22935    assert_eq!(
22936        1,
22937        requests_made.load(atomic::Ordering::Acquire),
22938        "Should query for colors once per editor open"
22939    );
22940    editor.update_in(cx, |editor, _, cx| {
22941        assert_eq!(
22942            vec![expected_color],
22943            extract_color_inlays(editor, cx),
22944            "Should have an initial inlay"
22945        );
22946    });
22947
22948    // opening another file in a split should not influence the LSP query counter
22949    workspace
22950        .update(cx, |workspace, window, cx| {
22951            assert_eq!(
22952                workspace.panes().len(),
22953                1,
22954                "Should have one pane with one editor"
22955            );
22956            workspace.move_item_to_pane_in_direction(
22957                &MoveItemToPaneInDirection {
22958                    direction: SplitDirection::Right,
22959                    focus: false,
22960                    clone: true,
22961                },
22962                window,
22963                cx,
22964            );
22965        })
22966        .unwrap();
22967    cx.run_until_parked();
22968    workspace
22969        .update(cx, |workspace, _, cx| {
22970            let panes = workspace.panes();
22971            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
22972            for pane in panes {
22973                let editor = pane
22974                    .read(cx)
22975                    .active_item()
22976                    .and_then(|item| item.downcast::<Editor>())
22977                    .expect("Should have opened an editor in each split");
22978                let editor_file = editor
22979                    .read(cx)
22980                    .buffer()
22981                    .read(cx)
22982                    .as_singleton()
22983                    .expect("test deals with singleton buffers")
22984                    .read(cx)
22985                    .file()
22986                    .expect("test buffese should have a file")
22987                    .path();
22988                assert_eq!(
22989                    editor_file.as_ref(),
22990                    Path::new("first.rs"),
22991                    "Both editors should be opened for the same file"
22992                )
22993            }
22994        })
22995        .unwrap();
22996
22997    cx.executor().advance_clock(Duration::from_millis(500));
22998    let save = editor.update_in(cx, |editor, window, cx| {
22999        editor.move_to_end(&MoveToEnd, window, cx);
23000        editor.handle_input("dirty", window, cx);
23001        editor.save(
23002            SaveOptions {
23003                format: true,
23004                autosave: true,
23005            },
23006            project.clone(),
23007            window,
23008            cx,
23009        )
23010    });
23011    save.await.unwrap();
23012
23013    color_request_handle.next().await.unwrap();
23014    cx.run_until_parked();
23015    assert_eq!(
23016        3,
23017        requests_made.load(atomic::Ordering::Acquire),
23018        "Should query for colors once per save and once per formatting after save"
23019    );
23020
23021    drop(editor);
23022    let close = workspace
23023        .update(cx, |workspace, window, cx| {
23024            workspace.active_pane().update(cx, |pane, cx| {
23025                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23026            })
23027        })
23028        .unwrap();
23029    close.await.unwrap();
23030    let close = workspace
23031        .update(cx, |workspace, window, cx| {
23032            workspace.active_pane().update(cx, |pane, cx| {
23033                pane.close_active_item(&CloseActiveItem::default(), window, cx)
23034            })
23035        })
23036        .unwrap();
23037    close.await.unwrap();
23038    assert_eq!(
23039        3,
23040        requests_made.load(atomic::Ordering::Acquire),
23041        "After saving and closing all editors, no extra requests should be made"
23042    );
23043    workspace
23044        .update(cx, |workspace, _, cx| {
23045            assert!(
23046                workspace.active_item(cx).is_none(),
23047                "Should close all editors"
23048            )
23049        })
23050        .unwrap();
23051
23052    workspace
23053        .update(cx, |workspace, window, cx| {
23054            workspace.active_pane().update(cx, |pane, cx| {
23055                pane.navigate_backward(window, cx);
23056            })
23057        })
23058        .unwrap();
23059    cx.executor().advance_clock(Duration::from_millis(100));
23060    cx.run_until_parked();
23061    let editor = workspace
23062        .update(cx, |workspace, _, cx| {
23063            workspace
23064                .active_item(cx)
23065                .expect("Should have reopened the editor again after navigating back")
23066                .downcast::<Editor>()
23067                .expect("Should be an editor")
23068        })
23069        .unwrap();
23070    color_request_handle.next().await.unwrap();
23071    assert_eq!(
23072        3,
23073        requests_made.load(atomic::Ordering::Acquire),
23074        "Cache should be reused on buffer close and reopen"
23075    );
23076    editor.update(cx, |editor, cx| {
23077        assert_eq!(
23078            vec![expected_color],
23079            extract_color_inlays(editor, cx),
23080            "Should have an initial inlay"
23081        );
23082    });
23083}
23084
23085#[gpui::test]
23086async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
23087    init_test(cx, |_| {});
23088    let (editor, cx) = cx.add_window_view(Editor::single_line);
23089    editor.update_in(cx, |editor, window, cx| {
23090        editor.set_text("oops\n\nwow\n", window, cx)
23091    });
23092    cx.run_until_parked();
23093    editor.update(cx, |editor, cx| {
23094        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
23095    });
23096    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
23097    cx.run_until_parked();
23098    editor.update(cx, |editor, cx| {
23099        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
23100    });
23101}
23102
23103#[track_caller]
23104fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
23105    editor
23106        .all_inlays(cx)
23107        .into_iter()
23108        .filter_map(|inlay| inlay.get_color())
23109        .map(Rgba::from)
23110        .collect()
23111}