editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    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, CloseOtherItems, 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();
   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(),
   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, 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, 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, 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, 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, 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, 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, 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_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1906    init_test(cx, |_| {});
 1907
 1908    let move_to_beg = MoveToBeginningOfLine {
 1909        stop_at_soft_wraps: true,
 1910        stop_at_indent: true,
 1911    };
 1912
 1913    let editor = cx.add_window(|window, cx| {
 1914        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1915        build_editor(buffer, window, cx)
 1916    });
 1917
 1918    _ = editor.update(cx, |editor, window, cx| {
 1919        // test cursor between line_start and indent_start
 1920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1921            s.select_display_ranges([
 1922                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1923            ]);
 1924        });
 1925
 1926        // cursor should move to line_start
 1927        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1928        assert_eq!(
 1929            editor.selections.display_ranges(cx),
 1930            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1931        );
 1932
 1933        // cursor should move to indent_start
 1934        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1935        assert_eq!(
 1936            editor.selections.display_ranges(cx),
 1937            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1938        );
 1939
 1940        // cursor should move to back to line_start
 1941        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1942        assert_eq!(
 1943            editor.selections.display_ranges(cx),
 1944            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1945        );
 1946    });
 1947}
 1948
 1949#[gpui::test]
 1950fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1951    init_test(cx, |_| {});
 1952
 1953    let editor = cx.add_window(|window, cx| {
 1954        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1955        build_editor(buffer, window, cx)
 1956    });
 1957    _ = editor.update(cx, |editor, window, cx| {
 1958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1959            s.select_display_ranges([
 1960                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1961                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1962            ])
 1963        });
 1964        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1965        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1966
 1967        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1968        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1969
 1970        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1971        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1972
 1973        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1974        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1975
 1976        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1977        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1978
 1979        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1980        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1981
 1982        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1983        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1987
 1988        editor.move_right(&MoveRight, window, cx);
 1989        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1990        assert_selection_ranges(
 1991            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1992            editor,
 1993            cx,
 1994        );
 1995
 1996        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1997        assert_selection_ranges(
 1998            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 1999            editor,
 2000            cx,
 2001        );
 2002
 2003        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2004        assert_selection_ranges(
 2005            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2006            editor,
 2007            cx,
 2008        );
 2009    });
 2010}
 2011
 2012#[gpui::test]
 2013fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2014    init_test(cx, |_| {});
 2015
 2016    let editor = cx.add_window(|window, cx| {
 2017        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2018        build_editor(buffer, window, cx)
 2019    });
 2020
 2021    _ = editor.update(cx, |editor, window, cx| {
 2022        editor.set_wrap_width(Some(140.0.into()), cx);
 2023        assert_eq!(
 2024            editor.display_text(cx),
 2025            "use one::{\n    two::three::\n    four::five\n};"
 2026        );
 2027
 2028        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2029            s.select_display_ranges([
 2030                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2031            ]);
 2032        });
 2033
 2034        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2035        assert_eq!(
 2036            editor.selections.display_ranges(cx),
 2037            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2038        );
 2039
 2040        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2041        assert_eq!(
 2042            editor.selections.display_ranges(cx),
 2043            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2044        );
 2045
 2046        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2047        assert_eq!(
 2048            editor.selections.display_ranges(cx),
 2049            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2050        );
 2051
 2052        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2053        assert_eq!(
 2054            editor.selections.display_ranges(cx),
 2055            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2056        );
 2057
 2058        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2059        assert_eq!(
 2060            editor.selections.display_ranges(cx),
 2061            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2062        );
 2063
 2064        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2065        assert_eq!(
 2066            editor.selections.display_ranges(cx),
 2067            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2068        );
 2069    });
 2070}
 2071
 2072#[gpui::test]
 2073async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2074    init_test(cx, |_| {});
 2075    let mut cx = EditorTestContext::new(cx).await;
 2076
 2077    let line_height = cx.editor(|editor, window, _| {
 2078        editor
 2079            .style()
 2080            .unwrap()
 2081            .text
 2082            .line_height_in_pixels(window.rem_size())
 2083    });
 2084    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2085
 2086    cx.set_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_end_of_paragraph(&MoveToEndOfParagraph, 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_end_of_paragraph(&MoveToEndOfParagraph, 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_end_of_paragraph(&MoveToEndOfParagraph, 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    cx.update_editor(|editor, window, cx| {
 2144        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2145    });
 2146    cx.assert_editor_state(
 2147        &r#"one
 2148        two
 2149
 2150        three
 2151        four
 2152        five
 2153        ˇ
 2154        six"#
 2155            .unindent(),
 2156    );
 2157
 2158    cx.update_editor(|editor, window, cx| {
 2159        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2160    });
 2161    cx.assert_editor_state(
 2162        &r#"one
 2163        two
 2164        ˇ
 2165        three
 2166        four
 2167        five
 2168
 2169        six"#
 2170            .unindent(),
 2171    );
 2172
 2173    cx.update_editor(|editor, window, cx| {
 2174        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2175    });
 2176    cx.assert_editor_state(
 2177        &r#"ˇone
 2178        two
 2179
 2180        three
 2181        four
 2182        five
 2183
 2184        six"#
 2185            .unindent(),
 2186    );
 2187}
 2188
 2189#[gpui::test]
 2190async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2191    init_test(cx, |_| {});
 2192    let mut cx = EditorTestContext::new(cx).await;
 2193    let line_height = cx.editor(|editor, window, _| {
 2194        editor
 2195            .style()
 2196            .unwrap()
 2197            .text
 2198            .line_height_in_pixels(window.rem_size())
 2199    });
 2200    let window = cx.window;
 2201    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2202
 2203    cx.set_state(
 2204        r#"ˇone
 2205        two
 2206        three
 2207        four
 2208        five
 2209        six
 2210        seven
 2211        eight
 2212        nine
 2213        ten
 2214        "#,
 2215    );
 2216
 2217    cx.update_editor(|editor, window, cx| {
 2218        assert_eq!(
 2219            editor.snapshot(window, cx).scroll_position(),
 2220            gpui::Point::new(0., 0.)
 2221        );
 2222        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2223        assert_eq!(
 2224            editor.snapshot(window, cx).scroll_position(),
 2225            gpui::Point::new(0., 3.)
 2226        );
 2227        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2228        assert_eq!(
 2229            editor.snapshot(window, cx).scroll_position(),
 2230            gpui::Point::new(0., 6.)
 2231        );
 2232        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2233        assert_eq!(
 2234            editor.snapshot(window, cx).scroll_position(),
 2235            gpui::Point::new(0., 3.)
 2236        );
 2237
 2238        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2239        assert_eq!(
 2240            editor.snapshot(window, cx).scroll_position(),
 2241            gpui::Point::new(0., 1.)
 2242        );
 2243        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2244        assert_eq!(
 2245            editor.snapshot(window, cx).scroll_position(),
 2246            gpui::Point::new(0., 3.)
 2247        );
 2248    });
 2249}
 2250
 2251#[gpui::test]
 2252async fn test_autoscroll(cx: &mut TestAppContext) {
 2253    init_test(cx, |_| {});
 2254    let mut cx = EditorTestContext::new(cx).await;
 2255
 2256    let line_height = cx.update_editor(|editor, window, cx| {
 2257        editor.set_vertical_scroll_margin(2, cx);
 2258        editor
 2259            .style()
 2260            .unwrap()
 2261            .text
 2262            .line_height_in_pixels(window.rem_size())
 2263    });
 2264    let window = cx.window;
 2265    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2266
 2267    cx.set_state(
 2268        r#"ˇone
 2269            two
 2270            three
 2271            four
 2272            five
 2273            six
 2274            seven
 2275            eight
 2276            nine
 2277            ten
 2278        "#,
 2279    );
 2280    cx.update_editor(|editor, window, cx| {
 2281        assert_eq!(
 2282            editor.snapshot(window, cx).scroll_position(),
 2283            gpui::Point::new(0., 0.0)
 2284        );
 2285    });
 2286
 2287    // Add a cursor below the visible area. Since both cursors cannot fit
 2288    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2289    // allows the vertical scroll margin below that cursor.
 2290    cx.update_editor(|editor, window, cx| {
 2291        editor.change_selections(Default::default(), window, cx, |selections| {
 2292            selections.select_ranges([
 2293                Point::new(0, 0)..Point::new(0, 0),
 2294                Point::new(6, 0)..Point::new(6, 0),
 2295            ]);
 2296        })
 2297    });
 2298    cx.update_editor(|editor, window, cx| {
 2299        assert_eq!(
 2300            editor.snapshot(window, cx).scroll_position(),
 2301            gpui::Point::new(0., 3.0)
 2302        );
 2303    });
 2304
 2305    // Move down. The editor cursor scrolls down to track the newest cursor.
 2306    cx.update_editor(|editor, window, cx| {
 2307        editor.move_down(&Default::default(), window, cx);
 2308    });
 2309    cx.update_editor(|editor, window, cx| {
 2310        assert_eq!(
 2311            editor.snapshot(window, cx).scroll_position(),
 2312            gpui::Point::new(0., 4.0)
 2313        );
 2314    });
 2315
 2316    // Add a cursor above the visible area. Since both cursors fit on screen,
 2317    // the editor scrolls to show both.
 2318    cx.update_editor(|editor, window, cx| {
 2319        editor.change_selections(Default::default(), window, cx, |selections| {
 2320            selections.select_ranges([
 2321                Point::new(1, 0)..Point::new(1, 0),
 2322                Point::new(6, 0)..Point::new(6, 0),
 2323            ]);
 2324        })
 2325    });
 2326    cx.update_editor(|editor, window, cx| {
 2327        assert_eq!(
 2328            editor.snapshot(window, cx).scroll_position(),
 2329            gpui::Point::new(0., 1.0)
 2330        );
 2331    });
 2332}
 2333
 2334#[gpui::test]
 2335async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2336    init_test(cx, |_| {});
 2337    let mut cx = EditorTestContext::new(cx).await;
 2338
 2339    let line_height = cx.editor(|editor, window, _cx| {
 2340        editor
 2341            .style()
 2342            .unwrap()
 2343            .text
 2344            .line_height_in_pixels(window.rem_size())
 2345    });
 2346    let window = cx.window;
 2347    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2348    cx.set_state(
 2349        &r#"
 2350        ˇone
 2351        two
 2352        threeˇ
 2353        four
 2354        five
 2355        six
 2356        seven
 2357        eight
 2358        nine
 2359        ten
 2360        "#
 2361        .unindent(),
 2362    );
 2363
 2364    cx.update_editor(|editor, window, cx| {
 2365        editor.move_page_down(&MovePageDown::default(), window, cx)
 2366    });
 2367    cx.assert_editor_state(
 2368        &r#"
 2369        one
 2370        two
 2371        three
 2372        ˇfour
 2373        five
 2374        sixˇ
 2375        seven
 2376        eight
 2377        nine
 2378        ten
 2379        "#
 2380        .unindent(),
 2381    );
 2382
 2383    cx.update_editor(|editor, window, cx| {
 2384        editor.move_page_down(&MovePageDown::default(), window, cx)
 2385    });
 2386    cx.assert_editor_state(
 2387        &r#"
 2388        one
 2389        two
 2390        three
 2391        four
 2392        five
 2393        six
 2394        ˇseven
 2395        eight
 2396        nineˇ
 2397        ten
 2398        "#
 2399        .unindent(),
 2400    );
 2401
 2402    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2403    cx.assert_editor_state(
 2404        &r#"
 2405        one
 2406        two
 2407        three
 2408        ˇfour
 2409        five
 2410        sixˇ
 2411        seven
 2412        eight
 2413        nine
 2414        ten
 2415        "#
 2416        .unindent(),
 2417    );
 2418
 2419    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2420    cx.assert_editor_state(
 2421        &r#"
 2422        ˇone
 2423        two
 2424        threeˇ
 2425        four
 2426        five
 2427        six
 2428        seven
 2429        eight
 2430        nine
 2431        ten
 2432        "#
 2433        .unindent(),
 2434    );
 2435
 2436    // Test select collapsing
 2437    cx.update_editor(|editor, window, cx| {
 2438        editor.move_page_down(&MovePageDown::default(), window, cx);
 2439        editor.move_page_down(&MovePageDown::default(), window, cx);
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441    });
 2442    cx.assert_editor_state(
 2443        &r#"
 2444        one
 2445        two
 2446        three
 2447        four
 2448        five
 2449        six
 2450        seven
 2451        eight
 2452        nine
 2453        ˇten
 2454        ˇ"#
 2455        .unindent(),
 2456    );
 2457}
 2458
 2459#[gpui::test]
 2460async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2461    init_test(cx, |_| {});
 2462    let mut cx = EditorTestContext::new(cx).await;
 2463    cx.set_state("one «two threeˇ» four");
 2464    cx.update_editor(|editor, window, cx| {
 2465        editor.delete_to_beginning_of_line(
 2466            &DeleteToBeginningOfLine {
 2467                stop_at_indent: false,
 2468            },
 2469            window,
 2470            cx,
 2471        );
 2472        assert_eq!(editor.text(cx), " four");
 2473    });
 2474}
 2475
 2476#[gpui::test]
 2477fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2478    init_test(cx, |_| {});
 2479
 2480    let editor = cx.add_window(|window, cx| {
 2481        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2482        build_editor(buffer, window, cx)
 2483    });
 2484
 2485    _ = editor.update(cx, |editor, window, cx| {
 2486        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2487            s.select_display_ranges([
 2488                // an empty selection - the preceding word fragment is deleted
 2489                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2490                // characters selected - they are deleted
 2491                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2492            ])
 2493        });
 2494        editor.delete_to_previous_word_start(
 2495            &DeleteToPreviousWordStart {
 2496                ignore_newlines: false,
 2497            },
 2498            window,
 2499            cx,
 2500        );
 2501        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2502    });
 2503
 2504    _ = editor.update(cx, |editor, window, cx| {
 2505        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2506            s.select_display_ranges([
 2507                // an empty selection - the following word fragment is deleted
 2508                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2509                // characters selected - they are deleted
 2510                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2511            ])
 2512        });
 2513        editor.delete_to_next_word_end(
 2514            &DeleteToNextWordEnd {
 2515                ignore_newlines: false,
 2516            },
 2517            window,
 2518            cx,
 2519        );
 2520        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2521    });
 2522}
 2523
 2524#[gpui::test]
 2525fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2526    init_test(cx, |_| {});
 2527
 2528    let editor = cx.add_window(|window, cx| {
 2529        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2530        build_editor(buffer, window, cx)
 2531    });
 2532    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2533        ignore_newlines: false,
 2534    };
 2535    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2536        ignore_newlines: true,
 2537    };
 2538
 2539    _ = editor.update(cx, |editor, window, cx| {
 2540        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2541            s.select_display_ranges([
 2542                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2543            ])
 2544        });
 2545        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2546        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2547        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2549        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2550        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2551        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2553        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2555        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2557    });
 2558}
 2559
 2560#[gpui::test]
 2561fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2562    init_test(cx, |_| {});
 2563
 2564    let editor = cx.add_window(|window, cx| {
 2565        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2566        build_editor(buffer, window, cx)
 2567    });
 2568    let del_to_next_word_end = DeleteToNextWordEnd {
 2569        ignore_newlines: false,
 2570    };
 2571    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2572        ignore_newlines: true,
 2573    };
 2574
 2575    _ = editor.update(cx, |editor, window, cx| {
 2576        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2577            s.select_display_ranges([
 2578                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2579            ])
 2580        });
 2581        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2582        assert_eq!(
 2583            editor.buffer.read(cx).read(cx).text(),
 2584            "one\n   two\nthree\n   four"
 2585        );
 2586        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2587        assert_eq!(
 2588            editor.buffer.read(cx).read(cx).text(),
 2589            "\n   two\nthree\n   four"
 2590        );
 2591        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2592        assert_eq!(
 2593            editor.buffer.read(cx).read(cx).text(),
 2594            "two\nthree\n   four"
 2595        );
 2596        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2597        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2598        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2599        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2600        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2601        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2602    });
 2603}
 2604
 2605#[gpui::test]
 2606fn test_newline(cx: &mut TestAppContext) {
 2607    init_test(cx, |_| {});
 2608
 2609    let editor = cx.add_window(|window, cx| {
 2610        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2611        build_editor(buffer, window, cx)
 2612    });
 2613
 2614    _ = editor.update(cx, |editor, window, cx| {
 2615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2616            s.select_display_ranges([
 2617                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2618                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2619                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2620            ])
 2621        });
 2622
 2623        editor.newline(&Newline, window, cx);
 2624        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2625    });
 2626}
 2627
 2628#[gpui::test]
 2629fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2630    init_test(cx, |_| {});
 2631
 2632    let editor = cx.add_window(|window, cx| {
 2633        let buffer = MultiBuffer::build_simple(
 2634            "
 2635                a
 2636                b(
 2637                    X
 2638                )
 2639                c(
 2640                    X
 2641                )
 2642            "
 2643            .unindent()
 2644            .as_str(),
 2645            cx,
 2646        );
 2647        let mut editor = build_editor(buffer, window, cx);
 2648        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2649            s.select_ranges([
 2650                Point::new(2, 4)..Point::new(2, 5),
 2651                Point::new(5, 4)..Point::new(5, 5),
 2652            ])
 2653        });
 2654        editor
 2655    });
 2656
 2657    _ = editor.update(cx, |editor, window, cx| {
 2658        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2659        editor.buffer.update(cx, |buffer, cx| {
 2660            buffer.edit(
 2661                [
 2662                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2663                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2664                ],
 2665                None,
 2666                cx,
 2667            );
 2668            assert_eq!(
 2669                buffer.read(cx).text(),
 2670                "
 2671                    a
 2672                    b()
 2673                    c()
 2674                "
 2675                .unindent()
 2676            );
 2677        });
 2678        assert_eq!(
 2679            editor.selections.ranges(cx),
 2680            &[
 2681                Point::new(1, 2)..Point::new(1, 2),
 2682                Point::new(2, 2)..Point::new(2, 2),
 2683            ],
 2684        );
 2685
 2686        editor.newline(&Newline, window, cx);
 2687        assert_eq!(
 2688            editor.text(cx),
 2689            "
 2690                a
 2691                b(
 2692                )
 2693                c(
 2694                )
 2695            "
 2696            .unindent()
 2697        );
 2698
 2699        // The selections are moved after the inserted newlines
 2700        assert_eq!(
 2701            editor.selections.ranges(cx),
 2702            &[
 2703                Point::new(2, 0)..Point::new(2, 0),
 2704                Point::new(4, 0)..Point::new(4, 0),
 2705            ],
 2706        );
 2707    });
 2708}
 2709
 2710#[gpui::test]
 2711async fn test_newline_above(cx: &mut TestAppContext) {
 2712    init_test(cx, |settings| {
 2713        settings.defaults.tab_size = NonZeroU32::new(4)
 2714    });
 2715
 2716    let language = Arc::new(
 2717        Language::new(
 2718            LanguageConfig::default(),
 2719            Some(tree_sitter_rust::LANGUAGE.into()),
 2720        )
 2721        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2722        .unwrap(),
 2723    );
 2724
 2725    let mut cx = EditorTestContext::new(cx).await;
 2726    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2727    cx.set_state(indoc! {"
 2728        const a: ˇA = (
 2729 2730                «const_functionˇ»(ˇ),
 2731                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2732 2733        ˇ);ˇ
 2734    "});
 2735
 2736    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2737    cx.assert_editor_state(indoc! {"
 2738        ˇ
 2739        const a: A = (
 2740            ˇ
 2741            (
 2742                ˇ
 2743                ˇ
 2744                const_function(),
 2745                ˇ
 2746                ˇ
 2747                ˇ
 2748                ˇ
 2749                something_else,
 2750                ˇ
 2751            )
 2752            ˇ
 2753            ˇ
 2754        );
 2755    "});
 2756}
 2757
 2758#[gpui::test]
 2759async fn test_newline_below(cx: &mut TestAppContext) {
 2760    init_test(cx, |settings| {
 2761        settings.defaults.tab_size = NonZeroU32::new(4)
 2762    });
 2763
 2764    let language = Arc::new(
 2765        Language::new(
 2766            LanguageConfig::default(),
 2767            Some(tree_sitter_rust::LANGUAGE.into()),
 2768        )
 2769        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2770        .unwrap(),
 2771    );
 2772
 2773    let mut cx = EditorTestContext::new(cx).await;
 2774    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2775    cx.set_state(indoc! {"
 2776        const a: ˇA = (
 2777 2778                «const_functionˇ»(ˇ),
 2779                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2780 2781        ˇ);ˇ
 2782    "});
 2783
 2784    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2785    cx.assert_editor_state(indoc! {"
 2786        const a: A = (
 2787            ˇ
 2788            (
 2789                ˇ
 2790                const_function(),
 2791                ˇ
 2792                ˇ
 2793                something_else,
 2794                ˇ
 2795                ˇ
 2796                ˇ
 2797                ˇ
 2798            )
 2799            ˇ
 2800        );
 2801        ˇ
 2802        ˇ
 2803    "});
 2804}
 2805
 2806#[gpui::test]
 2807async fn test_newline_comments(cx: &mut TestAppContext) {
 2808    init_test(cx, |settings| {
 2809        settings.defaults.tab_size = NonZeroU32::new(4)
 2810    });
 2811
 2812    let language = Arc::new(Language::new(
 2813        LanguageConfig {
 2814            line_comments: vec!["// ".into()],
 2815            ..LanguageConfig::default()
 2816        },
 2817        None,
 2818    ));
 2819    {
 2820        let mut cx = EditorTestContext::new(cx).await;
 2821        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2822        cx.set_state(indoc! {"
 2823        // Fooˇ
 2824    "});
 2825
 2826        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2827        cx.assert_editor_state(indoc! {"
 2828        // Foo
 2829        // ˇ
 2830    "});
 2831        // Ensure that we add comment prefix when existing line contains space
 2832        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2833        cx.assert_editor_state(
 2834            indoc! {"
 2835        // Foo
 2836        //s
 2837        // ˇ
 2838    "}
 2839            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2840            .as_str(),
 2841        );
 2842        // Ensure that we add comment prefix when existing line does not contain space
 2843        cx.set_state(indoc! {"
 2844        // Foo
 2845        //ˇ
 2846    "});
 2847        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2848        cx.assert_editor_state(indoc! {"
 2849        // Foo
 2850        //
 2851        // ˇ
 2852    "});
 2853        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2854        cx.set_state(indoc! {"
 2855        ˇ// Foo
 2856    "});
 2857        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2858        cx.assert_editor_state(indoc! {"
 2859
 2860        ˇ// Foo
 2861    "});
 2862    }
 2863    // Ensure that comment continuations can be disabled.
 2864    update_test_language_settings(cx, |settings| {
 2865        settings.defaults.extend_comment_on_newline = Some(false);
 2866    });
 2867    let mut cx = EditorTestContext::new(cx).await;
 2868    cx.set_state(indoc! {"
 2869        // Fooˇ
 2870    "});
 2871    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2872    cx.assert_editor_state(indoc! {"
 2873        // Foo
 2874        ˇ
 2875    "});
 2876}
 2877
 2878#[gpui::test]
 2879async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2880    init_test(cx, |settings| {
 2881        settings.defaults.tab_size = NonZeroU32::new(4)
 2882    });
 2883
 2884    let language = Arc::new(Language::new(
 2885        LanguageConfig {
 2886            line_comments: vec!["// ".into(), "/// ".into()],
 2887            ..LanguageConfig::default()
 2888        },
 2889        None,
 2890    ));
 2891    {
 2892        let mut cx = EditorTestContext::new(cx).await;
 2893        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2894        cx.set_state(indoc! {"
 2895        //ˇ
 2896    "});
 2897        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2898        cx.assert_editor_state(indoc! {"
 2899        //
 2900        // ˇ
 2901    "});
 2902
 2903        cx.set_state(indoc! {"
 2904        ///ˇ
 2905    "});
 2906        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2907        cx.assert_editor_state(indoc! {"
 2908        ///
 2909        /// ˇ
 2910    "});
 2911    }
 2912}
 2913
 2914#[gpui::test]
 2915async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2916    init_test(cx, |settings| {
 2917        settings.defaults.tab_size = NonZeroU32::new(4)
 2918    });
 2919
 2920    let language = Arc::new(
 2921        Language::new(
 2922            LanguageConfig {
 2923                documentation_comment: Some(language::BlockCommentConfig {
 2924                    start: "/**".into(),
 2925                    end: "*/".into(),
 2926                    prefix: "* ".into(),
 2927                    tab_size: 1,
 2928                }),
 2929
 2930                ..LanguageConfig::default()
 2931            },
 2932            Some(tree_sitter_rust::LANGUAGE.into()),
 2933        )
 2934        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2935        .unwrap(),
 2936    );
 2937
 2938    {
 2939        let mut cx = EditorTestContext::new(cx).await;
 2940        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2941        cx.set_state(indoc! {"
 2942        /**ˇ
 2943    "});
 2944
 2945        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2946        cx.assert_editor_state(indoc! {"
 2947        /**
 2948         * ˇ
 2949    "});
 2950        // Ensure that if cursor is before the comment start,
 2951        // we do not actually insert a comment prefix.
 2952        cx.set_state(indoc! {"
 2953        ˇ/**
 2954    "});
 2955        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2956        cx.assert_editor_state(indoc! {"
 2957
 2958        ˇ/**
 2959    "});
 2960        // Ensure that if cursor is between it doesn't add comment prefix.
 2961        cx.set_state(indoc! {"
 2962        /*ˇ*
 2963    "});
 2964        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2965        cx.assert_editor_state(indoc! {"
 2966        /*
 2967        ˇ*
 2968    "});
 2969        // Ensure that if suffix exists on same line after cursor it adds new line.
 2970        cx.set_state(indoc! {"
 2971        /**ˇ*/
 2972    "});
 2973        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2974        cx.assert_editor_state(indoc! {"
 2975        /**
 2976         * ˇ
 2977         */
 2978    "});
 2979        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2980        cx.set_state(indoc! {"
 2981        /**ˇ */
 2982    "});
 2983        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2984        cx.assert_editor_state(indoc! {"
 2985        /**
 2986         * ˇ
 2987         */
 2988    "});
 2989        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2990        cx.set_state(indoc! {"
 2991        /** ˇ*/
 2992    "});
 2993        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2994        cx.assert_editor_state(
 2995            indoc! {"
 2996        /**s
 2997         * ˇ
 2998         */
 2999    "}
 3000            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3001            .as_str(),
 3002        );
 3003        // Ensure that delimiter space is preserved when newline on already
 3004        // spaced delimiter.
 3005        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3006        cx.assert_editor_state(
 3007            indoc! {"
 3008        /**s
 3009         *s
 3010         * ˇ
 3011         */
 3012    "}
 3013            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3014            .as_str(),
 3015        );
 3016        // Ensure that delimiter space is preserved when space is not
 3017        // on existing delimiter.
 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        // Ensure that if suffix exists on same line after cursor it
 3031        // doesn't add extra new line if prefix is not on same line.
 3032        cx.set_state(indoc! {"
 3033        /**
 3034        ˇ*/
 3035    "});
 3036        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3037        cx.assert_editor_state(indoc! {"
 3038        /**
 3039
 3040        ˇ*/
 3041    "});
 3042        // Ensure that it detects suffix after existing prefix.
 3043        cx.set_state(indoc! {"
 3044        /**ˇ/
 3045    "});
 3046        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3047        cx.assert_editor_state(indoc! {"
 3048        /**
 3049        ˇ/
 3050    "});
 3051        // Ensure that if suffix exists on same line before
 3052        // cursor it does not add comment prefix.
 3053        cx.set_state(indoc! {"
 3054        /** */ˇ
 3055    "});
 3056        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3057        cx.assert_editor_state(indoc! {"
 3058        /** */
 3059        ˇ
 3060    "});
 3061        // Ensure that if suffix exists on same line before
 3062        // cursor it does not add comment prefix.
 3063        cx.set_state(indoc! {"
 3064        /**
 3065         *
 3066         */ˇ
 3067    "});
 3068        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3069        cx.assert_editor_state(indoc! {"
 3070        /**
 3071         *
 3072         */
 3073         ˇ
 3074    "});
 3075
 3076        // Ensure that inline comment followed by code
 3077        // doesn't add comment prefix on newline
 3078        cx.set_state(indoc! {"
 3079        /** */ textˇ
 3080    "});
 3081        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3082        cx.assert_editor_state(indoc! {"
 3083        /** */ text
 3084        ˇ
 3085    "});
 3086
 3087        // Ensure that text after comment end tag
 3088        // doesn't add comment prefix on newline
 3089        cx.set_state(indoc! {"
 3090        /**
 3091         *
 3092         */ˇtext
 3093    "});
 3094        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3095        cx.assert_editor_state(indoc! {"
 3096        /**
 3097         *
 3098         */
 3099         ˇtext
 3100    "});
 3101
 3102        // Ensure if not comment block it doesn't
 3103        // add comment prefix on newline
 3104        cx.set_state(indoc! {"
 3105        * textˇ
 3106    "});
 3107        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3108        cx.assert_editor_state(indoc! {"
 3109        * text
 3110        ˇ
 3111    "});
 3112    }
 3113    // Ensure that comment continuations can be disabled.
 3114    update_test_language_settings(cx, |settings| {
 3115        settings.defaults.extend_comment_on_newline = Some(false);
 3116    });
 3117    let mut cx = EditorTestContext::new(cx).await;
 3118    cx.set_state(indoc! {"
 3119        /**ˇ
 3120    "});
 3121    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3122    cx.assert_editor_state(indoc! {"
 3123        /**
 3124        ˇ
 3125    "});
 3126}
 3127
 3128#[gpui::test]
 3129async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3130    init_test(cx, |settings| {
 3131        settings.defaults.tab_size = NonZeroU32::new(4)
 3132    });
 3133
 3134    let lua_language = Arc::new(Language::new(
 3135        LanguageConfig {
 3136            line_comments: vec!["--".into()],
 3137            block_comment: Some(language::BlockCommentConfig {
 3138                start: "--[[".into(),
 3139                prefix: "".into(),
 3140                end: "]]".into(),
 3141                tab_size: 0,
 3142            }),
 3143            ..LanguageConfig::default()
 3144        },
 3145        None,
 3146    ));
 3147
 3148    let mut cx = EditorTestContext::new(cx).await;
 3149    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3150
 3151    // Line with line comment should extend
 3152    cx.set_state(indoc! {"
 3153        --ˇ
 3154    "});
 3155    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3156    cx.assert_editor_state(indoc! {"
 3157        --
 3158        --ˇ
 3159    "});
 3160
 3161    // Line with block comment that matches line comment should not extend
 3162    cx.set_state(indoc! {"
 3163        --[[ˇ
 3164    "});
 3165    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3166    cx.assert_editor_state(indoc! {"
 3167        --[[
 3168        ˇ
 3169    "});
 3170}
 3171
 3172#[gpui::test]
 3173fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3174    init_test(cx, |_| {});
 3175
 3176    let editor = cx.add_window(|window, cx| {
 3177        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3178        let mut editor = build_editor(buffer, window, cx);
 3179        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3180            s.select_ranges([3..4, 11..12, 19..20])
 3181        });
 3182        editor
 3183    });
 3184
 3185    _ = editor.update(cx, |editor, window, cx| {
 3186        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3187        editor.buffer.update(cx, |buffer, cx| {
 3188            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3189            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3190        });
 3191        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3192
 3193        editor.insert("Z", window, cx);
 3194        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3195
 3196        // The selections are moved after the inserted characters
 3197        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3198    });
 3199}
 3200
 3201#[gpui::test]
 3202async fn test_tab(cx: &mut TestAppContext) {
 3203    init_test(cx, |settings| {
 3204        settings.defaults.tab_size = NonZeroU32::new(3)
 3205    });
 3206
 3207    let mut cx = EditorTestContext::new(cx).await;
 3208    cx.set_state(indoc! {"
 3209        ˇabˇc
 3210        ˇ🏀ˇ🏀ˇefg
 3211 3212    "});
 3213    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3214    cx.assert_editor_state(indoc! {"
 3215           ˇab ˇc
 3216           ˇ🏀  ˇ🏀  ˇefg
 3217        d  ˇ
 3218    "});
 3219
 3220    cx.set_state(indoc! {"
 3221        a
 3222        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3223    "});
 3224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3225    cx.assert_editor_state(indoc! {"
 3226        a
 3227           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3228    "});
 3229}
 3230
 3231#[gpui::test]
 3232async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3233    init_test(cx, |_| {});
 3234
 3235    let mut cx = EditorTestContext::new(cx).await;
 3236    let language = Arc::new(
 3237        Language::new(
 3238            LanguageConfig::default(),
 3239            Some(tree_sitter_rust::LANGUAGE.into()),
 3240        )
 3241        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3242        .unwrap(),
 3243    );
 3244    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3245
 3246    // test when all cursors are not at suggested indent
 3247    // then simply move to their suggested indent location
 3248    cx.set_state(indoc! {"
 3249        const a: B = (
 3250            c(
 3251        ˇ
 3252        ˇ    )
 3253        );
 3254    "});
 3255    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3256    cx.assert_editor_state(indoc! {"
 3257        const a: B = (
 3258            c(
 3259                ˇ
 3260            ˇ)
 3261        );
 3262    "});
 3263
 3264    // test cursor already at suggested indent not moving when
 3265    // other cursors are yet to reach their suggested indents
 3266    cx.set_state(indoc! {"
 3267        ˇ
 3268        const a: B = (
 3269            c(
 3270                d(
 3271        ˇ
 3272                )
 3273        ˇ
 3274        ˇ    )
 3275        );
 3276    "});
 3277    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3278    cx.assert_editor_state(indoc! {"
 3279        ˇ
 3280        const a: B = (
 3281            c(
 3282                d(
 3283                    ˇ
 3284                )
 3285                ˇ
 3286            ˇ)
 3287        );
 3288    "});
 3289    // test when all cursors are at suggested indent then tab is inserted
 3290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3291    cx.assert_editor_state(indoc! {"
 3292            ˇ
 3293        const a: B = (
 3294            c(
 3295                d(
 3296                        ˇ
 3297                )
 3298                    ˇ
 3299                ˇ)
 3300        );
 3301    "});
 3302
 3303    // test when current indent is less than suggested indent,
 3304    // we adjust line to match suggested indent and move cursor to it
 3305    //
 3306    // when no other cursor is at word boundary, all of them should move
 3307    cx.set_state(indoc! {"
 3308        const a: B = (
 3309            c(
 3310                d(
 3311        ˇ
 3312        ˇ   )
 3313        ˇ   )
 3314        );
 3315    "});
 3316    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3317    cx.assert_editor_state(indoc! {"
 3318        const a: B = (
 3319            c(
 3320                d(
 3321                    ˇ
 3322                ˇ)
 3323            ˇ)
 3324        );
 3325    "});
 3326
 3327    // test when current indent is less than suggested indent,
 3328    // we adjust line to match suggested indent and move cursor to it
 3329    //
 3330    // when some other cursor is at word boundary, it should not move
 3331    cx.set_state(indoc! {"
 3332        const a: B = (
 3333            c(
 3334                d(
 3335        ˇ
 3336        ˇ   )
 3337           ˇ)
 3338        );
 3339    "});
 3340    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3341    cx.assert_editor_state(indoc! {"
 3342        const a: B = (
 3343            c(
 3344                d(
 3345                    ˇ
 3346                ˇ)
 3347            ˇ)
 3348        );
 3349    "});
 3350
 3351    // test when current indent is more than suggested indent,
 3352    // we just move cursor to current indent instead of suggested indent
 3353    //
 3354    // when no other cursor is at word boundary, all of them should move
 3355    cx.set_state(indoc! {"
 3356        const a: B = (
 3357            c(
 3358                d(
 3359        ˇ
 3360        ˇ                )
 3361        ˇ   )
 3362        );
 3363    "});
 3364    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3365    cx.assert_editor_state(indoc! {"
 3366        const a: B = (
 3367            c(
 3368                d(
 3369                    ˇ
 3370                        ˇ)
 3371            ˇ)
 3372        );
 3373    "});
 3374    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3375    cx.assert_editor_state(indoc! {"
 3376        const a: B = (
 3377            c(
 3378                d(
 3379                        ˇ
 3380                            ˇ)
 3381                ˇ)
 3382        );
 3383    "});
 3384
 3385    // test when current indent is more than suggested indent,
 3386    // we just move cursor to current indent instead of suggested indent
 3387    //
 3388    // when some other cursor is at word boundary, it doesn't move
 3389    cx.set_state(indoc! {"
 3390        const a: B = (
 3391            c(
 3392                d(
 3393        ˇ
 3394        ˇ                )
 3395            ˇ)
 3396        );
 3397    "});
 3398    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3399    cx.assert_editor_state(indoc! {"
 3400        const a: B = (
 3401            c(
 3402                d(
 3403                    ˇ
 3404                        ˇ)
 3405            ˇ)
 3406        );
 3407    "});
 3408
 3409    // handle auto-indent when there are multiple cursors on the same line
 3410    cx.set_state(indoc! {"
 3411        const a: B = (
 3412            c(
 3413        ˇ    ˇ
 3414        ˇ    )
 3415        );
 3416    "});
 3417    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        const a: B = (
 3420            c(
 3421                ˇ
 3422            ˇ)
 3423        );
 3424    "});
 3425}
 3426
 3427#[gpui::test]
 3428async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3429    init_test(cx, |settings| {
 3430        settings.defaults.tab_size = NonZeroU32::new(3)
 3431    });
 3432
 3433    let mut cx = EditorTestContext::new(cx).await;
 3434    cx.set_state(indoc! {"
 3435         ˇ
 3436        \t ˇ
 3437        \t  ˇ
 3438        \t   ˇ
 3439         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3440    "});
 3441
 3442    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3443    cx.assert_editor_state(indoc! {"
 3444           ˇ
 3445        \t   ˇ
 3446        \t   ˇ
 3447        \t      ˇ
 3448         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3449    "});
 3450}
 3451
 3452#[gpui::test]
 3453async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3454    init_test(cx, |settings| {
 3455        settings.defaults.tab_size = NonZeroU32::new(4)
 3456    });
 3457
 3458    let language = Arc::new(
 3459        Language::new(
 3460            LanguageConfig::default(),
 3461            Some(tree_sitter_rust::LANGUAGE.into()),
 3462        )
 3463        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3464        .unwrap(),
 3465    );
 3466
 3467    let mut cx = EditorTestContext::new(cx).await;
 3468    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3469    cx.set_state(indoc! {"
 3470        fn a() {
 3471            if b {
 3472        \t ˇc
 3473            }
 3474        }
 3475    "});
 3476
 3477    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3478    cx.assert_editor_state(indoc! {"
 3479        fn a() {
 3480            if b {
 3481                ˇc
 3482            }
 3483        }
 3484    "});
 3485}
 3486
 3487#[gpui::test]
 3488async fn test_indent_outdent(cx: &mut TestAppContext) {
 3489    init_test(cx, |settings| {
 3490        settings.defaults.tab_size = NonZeroU32::new(4);
 3491    });
 3492
 3493    let mut cx = EditorTestContext::new(cx).await;
 3494
 3495    cx.set_state(indoc! {"
 3496          «oneˇ» «twoˇ»
 3497        three
 3498         four
 3499    "});
 3500    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3501    cx.assert_editor_state(indoc! {"
 3502            «oneˇ» «twoˇ»
 3503        three
 3504         four
 3505    "});
 3506
 3507    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3508    cx.assert_editor_state(indoc! {"
 3509        «oneˇ» «twoˇ»
 3510        three
 3511         four
 3512    "});
 3513
 3514    // select across line ending
 3515    cx.set_state(indoc! {"
 3516        one two
 3517        t«hree
 3518        ˇ» four
 3519    "});
 3520    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3521    cx.assert_editor_state(indoc! {"
 3522        one two
 3523            t«hree
 3524        ˇ» four
 3525    "});
 3526
 3527    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3528    cx.assert_editor_state(indoc! {"
 3529        one two
 3530        t«hree
 3531        ˇ» four
 3532    "});
 3533
 3534    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3535    cx.set_state(indoc! {"
 3536        one two
 3537        ˇthree
 3538            four
 3539    "});
 3540    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3541    cx.assert_editor_state(indoc! {"
 3542        one two
 3543            ˇthree
 3544            four
 3545    "});
 3546
 3547    cx.set_state(indoc! {"
 3548        one two
 3549        ˇ    three
 3550            four
 3551    "});
 3552    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3553    cx.assert_editor_state(indoc! {"
 3554        one two
 3555        ˇthree
 3556            four
 3557    "});
 3558}
 3559
 3560#[gpui::test]
 3561async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3562    // This is a regression test for issue #33761
 3563    init_test(cx, |_| {});
 3564
 3565    let mut cx = EditorTestContext::new(cx).await;
 3566    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3568
 3569    cx.set_state(
 3570        r#"ˇ#     ingress:
 3571ˇ#         api:
 3572ˇ#             enabled: false
 3573ˇ#             pathType: Prefix
 3574ˇ#           console:
 3575ˇ#               enabled: false
 3576ˇ#               pathType: Prefix
 3577"#,
 3578    );
 3579
 3580    // Press tab to indent all lines
 3581    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3582
 3583    cx.assert_editor_state(
 3584        r#"    ˇ#     ingress:
 3585    ˇ#         api:
 3586    ˇ#             enabled: false
 3587    ˇ#             pathType: Prefix
 3588    ˇ#           console:
 3589    ˇ#               enabled: false
 3590    ˇ#               pathType: Prefix
 3591"#,
 3592    );
 3593}
 3594
 3595#[gpui::test]
 3596async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3597    // This is a test to make sure our fix for issue #33761 didn't break anything
 3598    init_test(cx, |_| {});
 3599
 3600    let mut cx = EditorTestContext::new(cx).await;
 3601    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3602    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3603
 3604    cx.set_state(
 3605        r#"ˇingress:
 3606ˇ  api:
 3607ˇ    enabled: false
 3608ˇ    pathType: Prefix
 3609"#,
 3610    );
 3611
 3612    // Press tab to indent all lines
 3613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3614
 3615    cx.assert_editor_state(
 3616        r#"ˇingress:
 3617    ˇapi:
 3618        ˇenabled: false
 3619        ˇpathType: Prefix
 3620"#,
 3621    );
 3622}
 3623
 3624#[gpui::test]
 3625async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3626    init_test(cx, |settings| {
 3627        settings.defaults.hard_tabs = Some(true);
 3628    });
 3629
 3630    let mut cx = EditorTestContext::new(cx).await;
 3631
 3632    // select two ranges on one line
 3633    cx.set_state(indoc! {"
 3634        «oneˇ» «twoˇ»
 3635        three
 3636        four
 3637    "});
 3638    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3639    cx.assert_editor_state(indoc! {"
 3640        \t«oneˇ» «twoˇ»
 3641        three
 3642        four
 3643    "});
 3644    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3645    cx.assert_editor_state(indoc! {"
 3646        \t\t«oneˇ» «twoˇ»
 3647        three
 3648        four
 3649    "});
 3650    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3651    cx.assert_editor_state(indoc! {"
 3652        \t«oneˇ» «twoˇ»
 3653        three
 3654        four
 3655    "});
 3656    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3657    cx.assert_editor_state(indoc! {"
 3658        «oneˇ» «twoˇ»
 3659        three
 3660        four
 3661    "});
 3662
 3663    // select across a line ending
 3664    cx.set_state(indoc! {"
 3665        one two
 3666        t«hree
 3667        ˇ»four
 3668    "});
 3669    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3670    cx.assert_editor_state(indoc! {"
 3671        one two
 3672        \tt«hree
 3673        ˇ»four
 3674    "});
 3675    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3676    cx.assert_editor_state(indoc! {"
 3677        one two
 3678        \t\tt«hree
 3679        ˇ»four
 3680    "});
 3681    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3682    cx.assert_editor_state(indoc! {"
 3683        one two
 3684        \tt«hree
 3685        ˇ»four
 3686    "});
 3687    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3688    cx.assert_editor_state(indoc! {"
 3689        one two
 3690        t«hree
 3691        ˇ»four
 3692    "});
 3693
 3694    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3695    cx.set_state(indoc! {"
 3696        one two
 3697        ˇthree
 3698        four
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        one two
 3703        ˇthree
 3704        four
 3705    "});
 3706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3707    cx.assert_editor_state(indoc! {"
 3708        one two
 3709        \tˇthree
 3710        four
 3711    "});
 3712    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3713    cx.assert_editor_state(indoc! {"
 3714        one two
 3715        ˇthree
 3716        four
 3717    "});
 3718}
 3719
 3720#[gpui::test]
 3721fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3722    init_test(cx, |settings| {
 3723        settings.languages.0.extend([
 3724            (
 3725                "TOML".into(),
 3726                LanguageSettingsContent {
 3727                    tab_size: NonZeroU32::new(2),
 3728                    ..Default::default()
 3729                },
 3730            ),
 3731            (
 3732                "Rust".into(),
 3733                LanguageSettingsContent {
 3734                    tab_size: NonZeroU32::new(4),
 3735                    ..Default::default()
 3736                },
 3737            ),
 3738        ]);
 3739    });
 3740
 3741    let toml_language = Arc::new(Language::new(
 3742        LanguageConfig {
 3743            name: "TOML".into(),
 3744            ..Default::default()
 3745        },
 3746        None,
 3747    ));
 3748    let rust_language = Arc::new(Language::new(
 3749        LanguageConfig {
 3750            name: "Rust".into(),
 3751            ..Default::default()
 3752        },
 3753        None,
 3754    ));
 3755
 3756    let toml_buffer =
 3757        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3758    let rust_buffer =
 3759        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3760    let multibuffer = cx.new(|cx| {
 3761        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3762        multibuffer.push_excerpts(
 3763            toml_buffer.clone(),
 3764            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3765            cx,
 3766        );
 3767        multibuffer.push_excerpts(
 3768            rust_buffer.clone(),
 3769            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3770            cx,
 3771        );
 3772        multibuffer
 3773    });
 3774
 3775    cx.add_window(|window, cx| {
 3776        let mut editor = build_editor(multibuffer, window, cx);
 3777
 3778        assert_eq!(
 3779            editor.text(cx),
 3780            indoc! {"
 3781                a = 1
 3782                b = 2
 3783
 3784                const c: usize = 3;
 3785            "}
 3786        );
 3787
 3788        select_ranges(
 3789            &mut editor,
 3790            indoc! {"
 3791                «aˇ» = 1
 3792                b = 2
 3793
 3794                «const c:ˇ» usize = 3;
 3795            "},
 3796            window,
 3797            cx,
 3798        );
 3799
 3800        editor.tab(&Tab, window, cx);
 3801        assert_text_with_selections(
 3802            &mut editor,
 3803            indoc! {"
 3804                  «aˇ» = 1
 3805                b = 2
 3806
 3807                    «const c:ˇ» usize = 3;
 3808            "},
 3809            cx,
 3810        );
 3811        editor.backtab(&Backtab, window, cx);
 3812        assert_text_with_selections(
 3813            &mut editor,
 3814            indoc! {"
 3815                «aˇ» = 1
 3816                b = 2
 3817
 3818                «const c:ˇ» usize = 3;
 3819            "},
 3820            cx,
 3821        );
 3822
 3823        editor
 3824    });
 3825}
 3826
 3827#[gpui::test]
 3828async fn test_backspace(cx: &mut TestAppContext) {
 3829    init_test(cx, |_| {});
 3830
 3831    let mut cx = EditorTestContext::new(cx).await;
 3832
 3833    // Basic backspace
 3834    cx.set_state(indoc! {"
 3835        onˇe two three
 3836        fou«rˇ» five six
 3837        seven «ˇeight nine
 3838        »ten
 3839    "});
 3840    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3841    cx.assert_editor_state(indoc! {"
 3842        oˇe two three
 3843        fouˇ five six
 3844        seven ˇten
 3845    "});
 3846
 3847    // Test backspace inside and around indents
 3848    cx.set_state(indoc! {"
 3849        zero
 3850            ˇone
 3851                ˇtwo
 3852            ˇ ˇ ˇ  three
 3853        ˇ  ˇ  four
 3854    "});
 3855    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3856    cx.assert_editor_state(indoc! {"
 3857        zero
 3858        ˇone
 3859            ˇtwo
 3860        ˇ  threeˇ  four
 3861    "});
 3862}
 3863
 3864#[gpui::test]
 3865async fn test_delete(cx: &mut TestAppContext) {
 3866    init_test(cx, |_| {});
 3867
 3868    let mut cx = EditorTestContext::new(cx).await;
 3869    cx.set_state(indoc! {"
 3870        onˇe two three
 3871        fou«rˇ» five six
 3872        seven «ˇeight nine
 3873        »ten
 3874    "});
 3875    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3876    cx.assert_editor_state(indoc! {"
 3877        onˇ two three
 3878        fouˇ five six
 3879        seven ˇten
 3880    "});
 3881}
 3882
 3883#[gpui::test]
 3884fn test_delete_line(cx: &mut TestAppContext) {
 3885    init_test(cx, |_| {});
 3886
 3887    let editor = cx.add_window(|window, cx| {
 3888        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3889        build_editor(buffer, window, cx)
 3890    });
 3891    _ = editor.update(cx, |editor, window, cx| {
 3892        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3893            s.select_display_ranges([
 3894                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3895                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3896                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3897            ])
 3898        });
 3899        editor.delete_line(&DeleteLine, window, cx);
 3900        assert_eq!(editor.display_text(cx), "ghi");
 3901        assert_eq!(
 3902            editor.selections.display_ranges(cx),
 3903            vec![
 3904                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3905                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3906            ]
 3907        );
 3908    });
 3909
 3910    let editor = cx.add_window(|window, cx| {
 3911        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3912        build_editor(buffer, window, cx)
 3913    });
 3914    _ = editor.update(cx, |editor, window, cx| {
 3915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3916            s.select_display_ranges([
 3917                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3918            ])
 3919        });
 3920        editor.delete_line(&DeleteLine, window, cx);
 3921        assert_eq!(editor.display_text(cx), "ghi\n");
 3922        assert_eq!(
 3923            editor.selections.display_ranges(cx),
 3924            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3925        );
 3926    });
 3927}
 3928
 3929#[gpui::test]
 3930fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3931    init_test(cx, |_| {});
 3932
 3933    cx.add_window(|window, cx| {
 3934        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3935        let mut editor = build_editor(buffer.clone(), window, cx);
 3936        let buffer = buffer.read(cx).as_singleton().unwrap();
 3937
 3938        assert_eq!(
 3939            editor.selections.ranges::<Point>(cx),
 3940            &[Point::new(0, 0)..Point::new(0, 0)]
 3941        );
 3942
 3943        // When on single line, replace newline at end by space
 3944        editor.join_lines(&JoinLines, window, cx);
 3945        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3946        assert_eq!(
 3947            editor.selections.ranges::<Point>(cx),
 3948            &[Point::new(0, 3)..Point::new(0, 3)]
 3949        );
 3950
 3951        // When multiple lines are selected, remove newlines that are spanned by the selection
 3952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3953            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3954        });
 3955        editor.join_lines(&JoinLines, window, cx);
 3956        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3957        assert_eq!(
 3958            editor.selections.ranges::<Point>(cx),
 3959            &[Point::new(0, 11)..Point::new(0, 11)]
 3960        );
 3961
 3962        // Undo should be transactional
 3963        editor.undo(&Undo, window, cx);
 3964        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3965        assert_eq!(
 3966            editor.selections.ranges::<Point>(cx),
 3967            &[Point::new(0, 5)..Point::new(2, 2)]
 3968        );
 3969
 3970        // When joining an empty line don't insert a space
 3971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3972            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3973        });
 3974        editor.join_lines(&JoinLines, window, cx);
 3975        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3976        assert_eq!(
 3977            editor.selections.ranges::<Point>(cx),
 3978            [Point::new(2, 3)..Point::new(2, 3)]
 3979        );
 3980
 3981        // We can remove trailing newlines
 3982        editor.join_lines(&JoinLines, window, cx);
 3983        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3984        assert_eq!(
 3985            editor.selections.ranges::<Point>(cx),
 3986            [Point::new(2, 3)..Point::new(2, 3)]
 3987        );
 3988
 3989        // We don't blow up on the last line
 3990        editor.join_lines(&JoinLines, window, cx);
 3991        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3992        assert_eq!(
 3993            editor.selections.ranges::<Point>(cx),
 3994            [Point::new(2, 3)..Point::new(2, 3)]
 3995        );
 3996
 3997        // reset to test indentation
 3998        editor.buffer.update(cx, |buffer, cx| {
 3999            buffer.edit(
 4000                [
 4001                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4002                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4003                ],
 4004                None,
 4005                cx,
 4006            )
 4007        });
 4008
 4009        // We remove any leading spaces
 4010        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4011        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4012            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4013        });
 4014        editor.join_lines(&JoinLines, window, cx);
 4015        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4016
 4017        // We don't insert a space for a line containing only spaces
 4018        editor.join_lines(&JoinLines, window, cx);
 4019        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4020
 4021        // We ignore any leading tabs
 4022        editor.join_lines(&JoinLines, window, cx);
 4023        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4024
 4025        editor
 4026    });
 4027}
 4028
 4029#[gpui::test]
 4030fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4031    init_test(cx, |_| {});
 4032
 4033    cx.add_window(|window, cx| {
 4034        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4035        let mut editor = build_editor(buffer.clone(), window, cx);
 4036        let buffer = buffer.read(cx).as_singleton().unwrap();
 4037
 4038        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4039            s.select_ranges([
 4040                Point::new(0, 2)..Point::new(1, 1),
 4041                Point::new(1, 2)..Point::new(1, 2),
 4042                Point::new(3, 1)..Point::new(3, 2),
 4043            ])
 4044        });
 4045
 4046        editor.join_lines(&JoinLines, window, cx);
 4047        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4048
 4049        assert_eq!(
 4050            editor.selections.ranges::<Point>(cx),
 4051            [
 4052                Point::new(0, 7)..Point::new(0, 7),
 4053                Point::new(1, 3)..Point::new(1, 3)
 4054            ]
 4055        );
 4056        editor
 4057    });
 4058}
 4059
 4060#[gpui::test]
 4061async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4062    init_test(cx, |_| {});
 4063
 4064    let mut cx = EditorTestContext::new(cx).await;
 4065
 4066    let diff_base = r#"
 4067        Line 0
 4068        Line 1
 4069        Line 2
 4070        Line 3
 4071        "#
 4072    .unindent();
 4073
 4074    cx.set_state(
 4075        &r#"
 4076        ˇLine 0
 4077        Line 1
 4078        Line 2
 4079        Line 3
 4080        "#
 4081        .unindent(),
 4082    );
 4083
 4084    cx.set_head_text(&diff_base);
 4085    executor.run_until_parked();
 4086
 4087    // Join lines
 4088    cx.update_editor(|editor, window, cx| {
 4089        editor.join_lines(&JoinLines, window, cx);
 4090    });
 4091    executor.run_until_parked();
 4092
 4093    cx.assert_editor_state(
 4094        &r#"
 4095        Line 0ˇ Line 1
 4096        Line 2
 4097        Line 3
 4098        "#
 4099        .unindent(),
 4100    );
 4101    // Join again
 4102    cx.update_editor(|editor, window, cx| {
 4103        editor.join_lines(&JoinLines, window, cx);
 4104    });
 4105    executor.run_until_parked();
 4106
 4107    cx.assert_editor_state(
 4108        &r#"
 4109        Line 0 Line 1ˇ Line 2
 4110        Line 3
 4111        "#
 4112        .unindent(),
 4113    );
 4114}
 4115
 4116#[gpui::test]
 4117async fn test_custom_newlines_cause_no_false_positive_diffs(
 4118    executor: BackgroundExecutor,
 4119    cx: &mut TestAppContext,
 4120) {
 4121    init_test(cx, |_| {});
 4122    let mut cx = EditorTestContext::new(cx).await;
 4123    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4124    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4125    executor.run_until_parked();
 4126
 4127    cx.update_editor(|editor, window, cx| {
 4128        let snapshot = editor.snapshot(window, cx);
 4129        assert_eq!(
 4130            snapshot
 4131                .buffer_snapshot
 4132                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4133                .collect::<Vec<_>>(),
 4134            Vec::new(),
 4135            "Should not have any diffs for files with custom newlines"
 4136        );
 4137    });
 4138}
 4139
 4140#[gpui::test]
 4141async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4142    init_test(cx, |_| {});
 4143
 4144    let mut cx = EditorTestContext::new(cx).await;
 4145
 4146    // Test sort_lines_case_insensitive()
 4147    cx.set_state(indoc! {"
 4148        «z
 4149        y
 4150        x
 4151        Z
 4152        Y
 4153        Xˇ»
 4154    "});
 4155    cx.update_editor(|e, window, cx| {
 4156        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4157    });
 4158    cx.assert_editor_state(indoc! {"
 4159        «x
 4160        X
 4161        y
 4162        Y
 4163        z
 4164        Zˇ»
 4165    "});
 4166
 4167    // Test sort_lines_by_length()
 4168    //
 4169    // Demonstrates:
 4170    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4171    // - sort is stable
 4172    cx.set_state(indoc! {"
 4173        «123
 4174        æ
 4175        12
 4176 4177        1
 4178        æˇ»
 4179    "});
 4180    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4181    cx.assert_editor_state(indoc! {"
 4182        «æ
 4183 4184        1
 4185        æ
 4186        12
 4187        123ˇ»
 4188    "});
 4189
 4190    // Test reverse_lines()
 4191    cx.set_state(indoc! {"
 4192        «5
 4193        4
 4194        3
 4195        2
 4196        1ˇ»
 4197    "});
 4198    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4199    cx.assert_editor_state(indoc! {"
 4200        «1
 4201        2
 4202        3
 4203        4
 4204        5ˇ»
 4205    "});
 4206
 4207    // Skip testing shuffle_line()
 4208
 4209    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4210    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4211
 4212    // Don't manipulate when cursor is on single line, but expand the selection
 4213    cx.set_state(indoc! {"
 4214        ddˇdd
 4215        ccc
 4216        bb
 4217        a
 4218    "});
 4219    cx.update_editor(|e, window, cx| {
 4220        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4221    });
 4222    cx.assert_editor_state(indoc! {"
 4223        «ddddˇ»
 4224        ccc
 4225        bb
 4226        a
 4227    "});
 4228
 4229    // Basic manipulate case
 4230    // Start selection moves to column 0
 4231    // End of selection shrinks to fit shorter line
 4232    cx.set_state(indoc! {"
 4233        dd«d
 4234        ccc
 4235        bb
 4236        aaaaaˇ»
 4237    "});
 4238    cx.update_editor(|e, window, cx| {
 4239        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4240    });
 4241    cx.assert_editor_state(indoc! {"
 4242        «aaaaa
 4243        bb
 4244        ccc
 4245        dddˇ»
 4246    "});
 4247
 4248    // Manipulate case with newlines
 4249    cx.set_state(indoc! {"
 4250        dd«d
 4251        ccc
 4252
 4253        bb
 4254        aaaaa
 4255
 4256        ˇ»
 4257    "});
 4258    cx.update_editor(|e, window, cx| {
 4259        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4260    });
 4261    cx.assert_editor_state(indoc! {"
 4262        «
 4263
 4264        aaaaa
 4265        bb
 4266        ccc
 4267        dddˇ»
 4268
 4269    "});
 4270
 4271    // Adding new line
 4272    cx.set_state(indoc! {"
 4273        aa«a
 4274        bbˇ»b
 4275    "});
 4276    cx.update_editor(|e, window, cx| {
 4277        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4278    });
 4279    cx.assert_editor_state(indoc! {"
 4280        «aaa
 4281        bbb
 4282        added_lineˇ»
 4283    "});
 4284
 4285    // Removing line
 4286    cx.set_state(indoc! {"
 4287        aa«a
 4288        bbbˇ»
 4289    "});
 4290    cx.update_editor(|e, window, cx| {
 4291        e.manipulate_immutable_lines(window, cx, |lines| {
 4292            lines.pop();
 4293        })
 4294    });
 4295    cx.assert_editor_state(indoc! {"
 4296        «aaaˇ»
 4297    "});
 4298
 4299    // Removing all lines
 4300    cx.set_state(indoc! {"
 4301        aa«a
 4302        bbbˇ»
 4303    "});
 4304    cx.update_editor(|e, window, cx| {
 4305        e.manipulate_immutable_lines(window, cx, |lines| {
 4306            lines.drain(..);
 4307        })
 4308    });
 4309    cx.assert_editor_state(indoc! {"
 4310        ˇ
 4311    "});
 4312}
 4313
 4314#[gpui::test]
 4315async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4316    init_test(cx, |_| {});
 4317
 4318    let mut cx = EditorTestContext::new(cx).await;
 4319
 4320    // Consider continuous selection as single selection
 4321    cx.set_state(indoc! {"
 4322        Aaa«aa
 4323        cˇ»c«c
 4324        bb
 4325        aaaˇ»aa
 4326    "});
 4327    cx.update_editor(|e, window, cx| {
 4328        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4329    });
 4330    cx.assert_editor_state(indoc! {"
 4331        «Aaaaa
 4332        ccc
 4333        bb
 4334        aaaaaˇ»
 4335    "});
 4336
 4337    cx.set_state(indoc! {"
 4338        Aaa«aa
 4339        cˇ»c«c
 4340        bb
 4341        aaaˇ»aa
 4342    "});
 4343    cx.update_editor(|e, window, cx| {
 4344        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4345    });
 4346    cx.assert_editor_state(indoc! {"
 4347        «Aaaaa
 4348        ccc
 4349        bbˇ»
 4350    "});
 4351
 4352    // Consider non continuous selection as distinct dedup operations
 4353    cx.set_state(indoc! {"
 4354        «aaaaa
 4355        bb
 4356        aaaaa
 4357        aaaaaˇ»
 4358
 4359        aaa«aaˇ»
 4360    "});
 4361    cx.update_editor(|e, window, cx| {
 4362        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4363    });
 4364    cx.assert_editor_state(indoc! {"
 4365        «aaaaa
 4366        bbˇ»
 4367
 4368        «aaaaaˇ»
 4369    "});
 4370}
 4371
 4372#[gpui::test]
 4373async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4374    init_test(cx, |_| {});
 4375
 4376    let mut cx = EditorTestContext::new(cx).await;
 4377
 4378    cx.set_state(indoc! {"
 4379        «Aaa
 4380        aAa
 4381        Aaaˇ»
 4382    "});
 4383    cx.update_editor(|e, window, cx| {
 4384        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4385    });
 4386    cx.assert_editor_state(indoc! {"
 4387        «Aaa
 4388        aAaˇ»
 4389    "});
 4390
 4391    cx.set_state(indoc! {"
 4392        «Aaa
 4393        aAa
 4394        aaAˇ»
 4395    "});
 4396    cx.update_editor(|e, window, cx| {
 4397        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4398    });
 4399    cx.assert_editor_state(indoc! {"
 4400        «Aaaˇ»
 4401    "});
 4402}
 4403
 4404#[gpui::test]
 4405async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4406    init_test(cx, |_| {});
 4407
 4408    let mut cx = EditorTestContext::new(cx).await;
 4409
 4410    // Manipulate with multiple selections on a single line
 4411    cx.set_state(indoc! {"
 4412        dd«dd
 4413        cˇ»c«c
 4414        bb
 4415        aaaˇ»aa
 4416    "});
 4417    cx.update_editor(|e, window, cx| {
 4418        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4419    });
 4420    cx.assert_editor_state(indoc! {"
 4421        «aaaaa
 4422        bb
 4423        ccc
 4424        ddddˇ»
 4425    "});
 4426
 4427    // Manipulate with multiple disjoin selections
 4428    cx.set_state(indoc! {"
 4429 4430        4
 4431        3
 4432        2
 4433        1ˇ»
 4434
 4435        dd«dd
 4436        ccc
 4437        bb
 4438        aaaˇ»aa
 4439    "});
 4440    cx.update_editor(|e, window, cx| {
 4441        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4442    });
 4443    cx.assert_editor_state(indoc! {"
 4444        «1
 4445        2
 4446        3
 4447        4
 4448        5ˇ»
 4449
 4450        «aaaaa
 4451        bb
 4452        ccc
 4453        ddddˇ»
 4454    "});
 4455
 4456    // Adding lines on each selection
 4457    cx.set_state(indoc! {"
 4458 4459        1ˇ»
 4460
 4461        bb«bb
 4462        aaaˇ»aa
 4463    "});
 4464    cx.update_editor(|e, window, cx| {
 4465        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4466    });
 4467    cx.assert_editor_state(indoc! {"
 4468        «2
 4469        1
 4470        added lineˇ»
 4471
 4472        «bbbb
 4473        aaaaa
 4474        added lineˇ»
 4475    "});
 4476
 4477    // Removing lines on each selection
 4478    cx.set_state(indoc! {"
 4479 4480        1ˇ»
 4481
 4482        bb«bb
 4483        aaaˇ»aa
 4484    "});
 4485    cx.update_editor(|e, window, cx| {
 4486        e.manipulate_immutable_lines(window, cx, |lines| {
 4487            lines.pop();
 4488        })
 4489    });
 4490    cx.assert_editor_state(indoc! {"
 4491        «2ˇ»
 4492
 4493        «bbbbˇ»
 4494    "});
 4495}
 4496
 4497#[gpui::test]
 4498async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4499    init_test(cx, |settings| {
 4500        settings.defaults.tab_size = NonZeroU32::new(3)
 4501    });
 4502
 4503    let mut cx = EditorTestContext::new(cx).await;
 4504
 4505    // MULTI SELECTION
 4506    // Ln.1 "«" tests empty lines
 4507    // Ln.9 tests just leading whitespace
 4508    cx.set_state(indoc! {"
 4509        «
 4510        abc                 // No indentationˇ»
 4511        «\tabc              // 1 tabˇ»
 4512        \t\tabc «      ˇ»   // 2 tabs
 4513        \t ab«c             // Tab followed by space
 4514         \tabc              // Space followed by tab (3 spaces should be the result)
 4515        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4516           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4517        \t
 4518        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4519    "});
 4520    cx.update_editor(|e, window, cx| {
 4521        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4522    });
 4523    cx.assert_editor_state(
 4524        indoc! {"
 4525            «
 4526            abc                 // No indentation
 4527               abc              // 1 tab
 4528                  abc          // 2 tabs
 4529                abc             // Tab followed by space
 4530               abc              // Space followed by tab (3 spaces should be the result)
 4531                           abc   // Mixed indentation (tab conversion depends on the column)
 4532               abc         // Already space indented
 4533               ·
 4534               abc\tdef          // Only the leading tab is manipulatedˇ»
 4535        "}
 4536        .replace("·", "")
 4537        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4538    );
 4539
 4540    // Test on just a few lines, the others should remain unchanged
 4541    // Only lines (3, 5, 10, 11) should change
 4542    cx.set_state(
 4543        indoc! {"
 4544            ·
 4545            abc                 // No indentation
 4546            \tabcˇ               // 1 tab
 4547            \t\tabc             // 2 tabs
 4548            \t abcˇ              // Tab followed by space
 4549             \tabc              // Space followed by tab (3 spaces should be the result)
 4550            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4551               abc              // Already space indented
 4552            «\t
 4553            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4554        "}
 4555        .replace("·", "")
 4556        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4557    );
 4558    cx.update_editor(|e, window, cx| {
 4559        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4560    });
 4561    cx.assert_editor_state(
 4562        indoc! {"
 4563            ·
 4564            abc                 // No indentation
 4565            «   abc               // 1 tabˇ»
 4566            \t\tabc             // 2 tabs
 4567            «    abc              // Tab followed by spaceˇ»
 4568             \tabc              // Space followed by tab (3 spaces should be the result)
 4569            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4570               abc              // Already space indented
 4571            «   ·
 4572               abc\tdef          // Only the leading tab is manipulatedˇ»
 4573        "}
 4574        .replace("·", "")
 4575        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4576    );
 4577
 4578    // SINGLE SELECTION
 4579    // Ln.1 "«" tests empty lines
 4580    // Ln.9 tests just leading whitespace
 4581    cx.set_state(indoc! {"
 4582        «
 4583        abc                 // No indentation
 4584        \tabc               // 1 tab
 4585        \t\tabc             // 2 tabs
 4586        \t abc              // Tab followed by space
 4587         \tabc              // Space followed by tab (3 spaces should be the result)
 4588        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4589           abc              // Already space indented
 4590        \t
 4591        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4592    "});
 4593    cx.update_editor(|e, window, cx| {
 4594        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4595    });
 4596    cx.assert_editor_state(
 4597        indoc! {"
 4598            «
 4599            abc                 // No indentation
 4600               abc               // 1 tab
 4601                  abc             // 2 tabs
 4602                abc              // Tab followed by space
 4603               abc              // Space followed by tab (3 spaces should be the result)
 4604                           abc   // Mixed indentation (tab conversion depends on the column)
 4605               abc              // Already space indented
 4606               ·
 4607               abc\tdef          // Only the leading tab is manipulatedˇ»
 4608        "}
 4609        .replace("·", "")
 4610        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4611    );
 4612}
 4613
 4614#[gpui::test]
 4615async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4616    init_test(cx, |settings| {
 4617        settings.defaults.tab_size = NonZeroU32::new(3)
 4618    });
 4619
 4620    let mut cx = EditorTestContext::new(cx).await;
 4621
 4622    // MULTI SELECTION
 4623    // Ln.1 "«" tests empty lines
 4624    // Ln.11 tests just leading whitespace
 4625    cx.set_state(indoc! {"
 4626        «
 4627        abˇ»ˇc                 // No indentation
 4628         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4629          abc  «             // 2 spaces (< 3 so dont convert)
 4630           abc              // 3 spaces (convert)
 4631             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4632        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4633        «\t abc              // Tab followed by space
 4634         \tabc              // Space followed by tab (should be consumed due to tab)
 4635        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4636           \tˇ»  «\t
 4637           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4638    "});
 4639    cx.update_editor(|e, window, cx| {
 4640        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4641    });
 4642    cx.assert_editor_state(indoc! {"
 4643        «
 4644        abc                 // No indentation
 4645         abc                // 1 space (< 3 so dont convert)
 4646          abc               // 2 spaces (< 3 so dont convert)
 4647        \tabc              // 3 spaces (convert)
 4648        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4649        \t\t\tabc           // Already tab indented
 4650        \t abc              // Tab followed by space
 4651        \tabc              // Space followed by tab (should be consumed due to tab)
 4652        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4653        \t\t\t
 4654        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4655    "});
 4656
 4657    // Test on just a few lines, the other should remain unchanged
 4658    // Only lines (4, 8, 11, 12) should change
 4659    cx.set_state(
 4660        indoc! {"
 4661            ·
 4662            abc                 // No indentation
 4663             abc                // 1 space (< 3 so dont convert)
 4664              abc               // 2 spaces (< 3 so dont convert)
 4665            «   abc              // 3 spaces (convert)ˇ»
 4666                 abc            // 5 spaces (1 tab + 2 spaces)
 4667            \t\t\tabc           // Already tab indented
 4668            \t abc              // Tab followed by space
 4669             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4670               \t\t  \tabc      // Mixed indentation
 4671            \t \t  \t   \tabc   // Mixed indentation
 4672               \t  \tˇ
 4673            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4674        "}
 4675        .replace("·", "")
 4676        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4677    );
 4678    cx.update_editor(|e, window, cx| {
 4679        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4680    });
 4681    cx.assert_editor_state(
 4682        indoc! {"
 4683            ·
 4684            abc                 // No indentation
 4685             abc                // 1 space (< 3 so dont convert)
 4686              abc               // 2 spaces (< 3 so dont convert)
 4687            «\tabc              // 3 spaces (convert)ˇ»
 4688                 abc            // 5 spaces (1 tab + 2 spaces)
 4689            \t\t\tabc           // Already tab indented
 4690            \t abc              // Tab followed by space
 4691            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4692               \t\t  \tabc      // Mixed indentation
 4693            \t \t  \t   \tabc   // Mixed indentation
 4694            «\t\t\t
 4695            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4696        "}
 4697        .replace("·", "")
 4698        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4699    );
 4700
 4701    // SINGLE SELECTION
 4702    // Ln.1 "«" tests empty lines
 4703    // Ln.11 tests just leading whitespace
 4704    cx.set_state(indoc! {"
 4705        «
 4706        abc                 // No indentation
 4707         abc                // 1 space (< 3 so dont convert)
 4708          abc               // 2 spaces (< 3 so dont convert)
 4709           abc              // 3 spaces (convert)
 4710             abc            // 5 spaces (1 tab + 2 spaces)
 4711        \t\t\tabc           // Already tab indented
 4712        \t abc              // Tab followed by space
 4713         \tabc              // Space followed by tab (should be consumed due to tab)
 4714        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4715           \t  \t
 4716           abc   \t         // Only the leading spaces should be convertedˇ»
 4717    "});
 4718    cx.update_editor(|e, window, cx| {
 4719        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4720    });
 4721    cx.assert_editor_state(indoc! {"
 4722        «
 4723        abc                 // No indentation
 4724         abc                // 1 space (< 3 so dont convert)
 4725          abc               // 2 spaces (< 3 so dont convert)
 4726        \tabc              // 3 spaces (convert)
 4727        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4728        \t\t\tabc           // Already tab indented
 4729        \t abc              // Tab followed by space
 4730        \tabc              // Space followed by tab (should be consumed due to tab)
 4731        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4732        \t\t\t
 4733        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4734    "});
 4735}
 4736
 4737#[gpui::test]
 4738async fn test_toggle_case(cx: &mut TestAppContext) {
 4739    init_test(cx, |_| {});
 4740
 4741    let mut cx = EditorTestContext::new(cx).await;
 4742
 4743    // If all lower case -> upper case
 4744    cx.set_state(indoc! {"
 4745        «hello worldˇ»
 4746    "});
 4747    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4748    cx.assert_editor_state(indoc! {"
 4749        «HELLO WORLDˇ»
 4750    "});
 4751
 4752    // If all upper case -> lower case
 4753    cx.set_state(indoc! {"
 4754        «HELLO WORLDˇ»
 4755    "});
 4756    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4757    cx.assert_editor_state(indoc! {"
 4758        «hello worldˇ»
 4759    "});
 4760
 4761    // If any upper case characters are identified -> lower case
 4762    // This matches JetBrains IDEs
 4763    cx.set_state(indoc! {"
 4764        «hEllo worldˇ»
 4765    "});
 4766    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4767    cx.assert_editor_state(indoc! {"
 4768        «hello worldˇ»
 4769    "});
 4770}
 4771
 4772#[gpui::test]
 4773async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4774    init_test(cx, |_| {});
 4775
 4776    let mut cx = EditorTestContext::new(cx).await;
 4777
 4778    cx.set_state(indoc! {"
 4779        «implement-windows-supportˇ»
 4780    "});
 4781    cx.update_editor(|e, window, cx| {
 4782        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4783    });
 4784    cx.assert_editor_state(indoc! {"
 4785        «Implement windows supportˇ»
 4786    "});
 4787}
 4788
 4789#[gpui::test]
 4790async fn test_manipulate_text(cx: &mut TestAppContext) {
 4791    init_test(cx, |_| {});
 4792
 4793    let mut cx = EditorTestContext::new(cx).await;
 4794
 4795    // Test convert_to_upper_case()
 4796    cx.set_state(indoc! {"
 4797        «hello worldˇ»
 4798    "});
 4799    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4800    cx.assert_editor_state(indoc! {"
 4801        «HELLO WORLDˇ»
 4802    "});
 4803
 4804    // Test convert_to_lower_case()
 4805    cx.set_state(indoc! {"
 4806        «HELLO WORLDˇ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «hello worldˇ»
 4811    "});
 4812
 4813    // Test multiple line, single selection case
 4814    cx.set_state(indoc! {"
 4815        «The quick brown
 4816        fox jumps over
 4817        the lazy dogˇ»
 4818    "});
 4819    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4820    cx.assert_editor_state(indoc! {"
 4821        «The Quick Brown
 4822        Fox Jumps Over
 4823        The Lazy Dogˇ»
 4824    "});
 4825
 4826    // Test multiple line, single selection case
 4827    cx.set_state(indoc! {"
 4828        «The quick brown
 4829        fox jumps over
 4830        the lazy dogˇ»
 4831    "});
 4832    cx.update_editor(|e, window, cx| {
 4833        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4834    });
 4835    cx.assert_editor_state(indoc! {"
 4836        «TheQuickBrown
 4837        FoxJumpsOver
 4838        TheLazyDogˇ»
 4839    "});
 4840
 4841    // From here on out, test more complex cases of manipulate_text()
 4842
 4843    // Test no selection case - should affect words cursors are in
 4844    // Cursor at beginning, middle, and end of word
 4845    cx.set_state(indoc! {"
 4846        ˇhello big beauˇtiful worldˇ
 4847    "});
 4848    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4849    cx.assert_editor_state(indoc! {"
 4850        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4851    "});
 4852
 4853    // Test multiple selections on a single line and across multiple lines
 4854    cx.set_state(indoc! {"
 4855        «Theˇ» quick «brown
 4856        foxˇ» jumps «overˇ»
 4857        the «lazyˇ» dog
 4858    "});
 4859    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4860    cx.assert_editor_state(indoc! {"
 4861        «THEˇ» quick «BROWN
 4862        FOXˇ» jumps «OVERˇ»
 4863        the «LAZYˇ» dog
 4864    "});
 4865
 4866    // Test case where text length grows
 4867    cx.set_state(indoc! {"
 4868        «tschüߡ»
 4869    "});
 4870    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4871    cx.assert_editor_state(indoc! {"
 4872        «TSCHÜSSˇ»
 4873    "});
 4874
 4875    // Test to make sure we don't crash when text shrinks
 4876    cx.set_state(indoc! {"
 4877        aaa_bbbˇ
 4878    "});
 4879    cx.update_editor(|e, window, cx| {
 4880        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4881    });
 4882    cx.assert_editor_state(indoc! {"
 4883        «aaaBbbˇ»
 4884    "});
 4885
 4886    // Test to make sure we all aware of the fact that each word can grow and shrink
 4887    // Final selections should be aware of this fact
 4888    cx.set_state(indoc! {"
 4889        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4890    "});
 4891    cx.update_editor(|e, window, cx| {
 4892        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4893    });
 4894    cx.assert_editor_state(indoc! {"
 4895        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4896    "});
 4897
 4898    cx.set_state(indoc! {"
 4899        «hElLo, WoRld!ˇ»
 4900    "});
 4901    cx.update_editor(|e, window, cx| {
 4902        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4903    });
 4904    cx.assert_editor_state(indoc! {"
 4905        «HeLlO, wOrLD!ˇ»
 4906    "});
 4907}
 4908
 4909#[gpui::test]
 4910fn test_duplicate_line(cx: &mut TestAppContext) {
 4911    init_test(cx, |_| {});
 4912
 4913    let editor = cx.add_window(|window, cx| {
 4914        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4915        build_editor(buffer, window, cx)
 4916    });
 4917    _ = editor.update(cx, |editor, window, cx| {
 4918        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4919            s.select_display_ranges([
 4920                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4921                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4922                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4923                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4924            ])
 4925        });
 4926        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4927        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4928        assert_eq!(
 4929            editor.selections.display_ranges(cx),
 4930            vec![
 4931                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4932                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4933                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4934                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4935            ]
 4936        );
 4937    });
 4938
 4939    let editor = cx.add_window(|window, cx| {
 4940        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4941        build_editor(buffer, window, cx)
 4942    });
 4943    _ = editor.update(cx, |editor, window, cx| {
 4944        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4945            s.select_display_ranges([
 4946                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4947                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4948            ])
 4949        });
 4950        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4951        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4952        assert_eq!(
 4953            editor.selections.display_ranges(cx),
 4954            vec![
 4955                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4956                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4957            ]
 4958        );
 4959    });
 4960
 4961    // With `move_upwards` the selections stay in place, except for
 4962    // the lines inserted above them
 4963    let editor = cx.add_window(|window, cx| {
 4964        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4965        build_editor(buffer, window, cx)
 4966    });
 4967    _ = editor.update(cx, |editor, window, cx| {
 4968        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4969            s.select_display_ranges([
 4970                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4971                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4972                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4973                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4974            ])
 4975        });
 4976        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4977        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4978        assert_eq!(
 4979            editor.selections.display_ranges(cx),
 4980            vec![
 4981                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4982                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4983                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4984                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4985            ]
 4986        );
 4987    });
 4988
 4989    let editor = cx.add_window(|window, cx| {
 4990        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4991        build_editor(buffer, window, cx)
 4992    });
 4993    _ = editor.update(cx, |editor, window, cx| {
 4994        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4995            s.select_display_ranges([
 4996                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4997                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4998            ])
 4999        });
 5000        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5001        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5002        assert_eq!(
 5003            editor.selections.display_ranges(cx),
 5004            vec![
 5005                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5006                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5007            ]
 5008        );
 5009    });
 5010
 5011    let editor = cx.add_window(|window, cx| {
 5012        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5013        build_editor(buffer, window, cx)
 5014    });
 5015    _ = editor.update(cx, |editor, window, cx| {
 5016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5017            s.select_display_ranges([
 5018                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5019                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5020            ])
 5021        });
 5022        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5023        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5024        assert_eq!(
 5025            editor.selections.display_ranges(cx),
 5026            vec![
 5027                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5028                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5029            ]
 5030        );
 5031    });
 5032}
 5033
 5034#[gpui::test]
 5035fn test_move_line_up_down(cx: &mut TestAppContext) {
 5036    init_test(cx, |_| {});
 5037
 5038    let editor = cx.add_window(|window, cx| {
 5039        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5040        build_editor(buffer, window, cx)
 5041    });
 5042    _ = editor.update(cx, |editor, window, cx| {
 5043        editor.fold_creases(
 5044            vec![
 5045                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5046                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5047                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5048            ],
 5049            true,
 5050            window,
 5051            cx,
 5052        );
 5053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5054            s.select_display_ranges([
 5055                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5056                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5057                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5058                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5059            ])
 5060        });
 5061        assert_eq!(
 5062            editor.display_text(cx),
 5063            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5064        );
 5065
 5066        editor.move_line_up(&MoveLineUp, window, cx);
 5067        assert_eq!(
 5068            editor.display_text(cx),
 5069            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5070        );
 5071        assert_eq!(
 5072            editor.selections.display_ranges(cx),
 5073            vec![
 5074                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5075                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5076                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5077                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5078            ]
 5079        );
 5080    });
 5081
 5082    _ = editor.update(cx, |editor, window, cx| {
 5083        editor.move_line_down(&MoveLineDown, window, cx);
 5084        assert_eq!(
 5085            editor.display_text(cx),
 5086            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5087        );
 5088        assert_eq!(
 5089            editor.selections.display_ranges(cx),
 5090            vec![
 5091                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5092                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5093                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5094                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5095            ]
 5096        );
 5097    });
 5098
 5099    _ = editor.update(cx, |editor, window, cx| {
 5100        editor.move_line_down(&MoveLineDown, window, cx);
 5101        assert_eq!(
 5102            editor.display_text(cx),
 5103            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5104        );
 5105        assert_eq!(
 5106            editor.selections.display_ranges(cx),
 5107            vec![
 5108                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5109                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5110                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5111                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5112            ]
 5113        );
 5114    });
 5115
 5116    _ = editor.update(cx, |editor, window, cx| {
 5117        editor.move_line_up(&MoveLineUp, window, cx);
 5118        assert_eq!(
 5119            editor.display_text(cx),
 5120            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5121        );
 5122        assert_eq!(
 5123            editor.selections.display_ranges(cx),
 5124            vec![
 5125                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5126                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5127                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5128                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5129            ]
 5130        );
 5131    });
 5132}
 5133
 5134#[gpui::test]
 5135fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5136    init_test(cx, |_| {});
 5137    let editor = cx.add_window(|window, cx| {
 5138        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5139        build_editor(buffer, window, cx)
 5140    });
 5141    _ = editor.update(cx, |editor, window, cx| {
 5142        editor.fold_creases(
 5143            vec![Crease::simple(
 5144                Point::new(6, 4)..Point::new(7, 4),
 5145                FoldPlaceholder::test(),
 5146            )],
 5147            true,
 5148            window,
 5149            cx,
 5150        );
 5151        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5152            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5153        });
 5154        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5155        editor.move_line_up(&MoveLineUp, window, cx);
 5156        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5157        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5158    });
 5159}
 5160
 5161#[gpui::test]
 5162fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5163    init_test(cx, |_| {});
 5164
 5165    let editor = cx.add_window(|window, cx| {
 5166        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5167        build_editor(buffer, window, cx)
 5168    });
 5169    _ = editor.update(cx, |editor, window, cx| {
 5170        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5171        editor.insert_blocks(
 5172            [BlockProperties {
 5173                style: BlockStyle::Fixed,
 5174                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5175                height: Some(1),
 5176                render: Arc::new(|_| div().into_any()),
 5177                priority: 0,
 5178            }],
 5179            Some(Autoscroll::fit()),
 5180            cx,
 5181        );
 5182        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5183            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5184        });
 5185        editor.move_line_down(&MoveLineDown, window, cx);
 5186    });
 5187}
 5188
 5189#[gpui::test]
 5190async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5191    init_test(cx, |_| {});
 5192
 5193    let mut cx = EditorTestContext::new(cx).await;
 5194    cx.set_state(
 5195        &"
 5196            ˇzero
 5197            one
 5198            two
 5199            three
 5200            four
 5201            five
 5202        "
 5203        .unindent(),
 5204    );
 5205
 5206    // Create a four-line block that replaces three lines of text.
 5207    cx.update_editor(|editor, window, cx| {
 5208        let snapshot = editor.snapshot(window, cx);
 5209        let snapshot = &snapshot.buffer_snapshot;
 5210        let placement = BlockPlacement::Replace(
 5211            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5212        );
 5213        editor.insert_blocks(
 5214            [BlockProperties {
 5215                placement,
 5216                height: Some(4),
 5217                style: BlockStyle::Sticky,
 5218                render: Arc::new(|_| gpui::div().into_any_element()),
 5219                priority: 0,
 5220            }],
 5221            None,
 5222            cx,
 5223        );
 5224    });
 5225
 5226    // Move down so that the cursor touches the block.
 5227    cx.update_editor(|editor, window, cx| {
 5228        editor.move_down(&Default::default(), window, cx);
 5229    });
 5230    cx.assert_editor_state(
 5231        &"
 5232            zero
 5233            «one
 5234            two
 5235            threeˇ»
 5236            four
 5237            five
 5238        "
 5239        .unindent(),
 5240    );
 5241
 5242    // Move down past the block.
 5243    cx.update_editor(|editor, window, cx| {
 5244        editor.move_down(&Default::default(), window, cx);
 5245    });
 5246    cx.assert_editor_state(
 5247        &"
 5248            zero
 5249            one
 5250            two
 5251            three
 5252            ˇfour
 5253            five
 5254        "
 5255        .unindent(),
 5256    );
 5257}
 5258
 5259#[gpui::test]
 5260fn test_transpose(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262
 5263    _ = cx.add_window(|window, cx| {
 5264        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5265        editor.set_style(EditorStyle::default(), window, cx);
 5266        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5267            s.select_ranges([1..1])
 5268        });
 5269        editor.transpose(&Default::default(), window, cx);
 5270        assert_eq!(editor.text(cx), "bac");
 5271        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5272
 5273        editor.transpose(&Default::default(), window, cx);
 5274        assert_eq!(editor.text(cx), "bca");
 5275        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5276
 5277        editor.transpose(&Default::default(), window, cx);
 5278        assert_eq!(editor.text(cx), "bac");
 5279        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5280
 5281        editor
 5282    });
 5283
 5284    _ = cx.add_window(|window, cx| {
 5285        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5286        editor.set_style(EditorStyle::default(), window, cx);
 5287        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5288            s.select_ranges([3..3])
 5289        });
 5290        editor.transpose(&Default::default(), window, cx);
 5291        assert_eq!(editor.text(cx), "acb\nde");
 5292        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5293
 5294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5295            s.select_ranges([4..4])
 5296        });
 5297        editor.transpose(&Default::default(), window, cx);
 5298        assert_eq!(editor.text(cx), "acbd\ne");
 5299        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5300
 5301        editor.transpose(&Default::default(), window, cx);
 5302        assert_eq!(editor.text(cx), "acbde\n");
 5303        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5304
 5305        editor.transpose(&Default::default(), window, cx);
 5306        assert_eq!(editor.text(cx), "acbd\ne");
 5307        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5308
 5309        editor
 5310    });
 5311
 5312    _ = cx.add_window(|window, cx| {
 5313        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5314        editor.set_style(EditorStyle::default(), window, cx);
 5315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5316            s.select_ranges([1..1, 2..2, 4..4])
 5317        });
 5318        editor.transpose(&Default::default(), window, cx);
 5319        assert_eq!(editor.text(cx), "bacd\ne");
 5320        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5321
 5322        editor.transpose(&Default::default(), window, cx);
 5323        assert_eq!(editor.text(cx), "bcade\n");
 5324        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5325
 5326        editor.transpose(&Default::default(), window, cx);
 5327        assert_eq!(editor.text(cx), "bcda\ne");
 5328        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5329
 5330        editor.transpose(&Default::default(), window, cx);
 5331        assert_eq!(editor.text(cx), "bcade\n");
 5332        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5333
 5334        editor.transpose(&Default::default(), window, cx);
 5335        assert_eq!(editor.text(cx), "bcaed\n");
 5336        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5337
 5338        editor
 5339    });
 5340
 5341    _ = cx.add_window(|window, cx| {
 5342        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5343        editor.set_style(EditorStyle::default(), window, cx);
 5344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5345            s.select_ranges([4..4])
 5346        });
 5347        editor.transpose(&Default::default(), window, cx);
 5348        assert_eq!(editor.text(cx), "🏀🍐✋");
 5349        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5350
 5351        editor.transpose(&Default::default(), window, cx);
 5352        assert_eq!(editor.text(cx), "🏀✋🍐");
 5353        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5354
 5355        editor.transpose(&Default::default(), window, cx);
 5356        assert_eq!(editor.text(cx), "🏀🍐✋");
 5357        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5358
 5359        editor
 5360    });
 5361}
 5362
 5363#[gpui::test]
 5364async fn test_rewrap(cx: &mut TestAppContext) {
 5365    init_test(cx, |settings| {
 5366        settings.languages.0.extend([
 5367            (
 5368                "Markdown".into(),
 5369                LanguageSettingsContent {
 5370                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5371                    preferred_line_length: Some(40),
 5372                    ..Default::default()
 5373                },
 5374            ),
 5375            (
 5376                "Plain Text".into(),
 5377                LanguageSettingsContent {
 5378                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5379                    preferred_line_length: Some(40),
 5380                    ..Default::default()
 5381                },
 5382            ),
 5383            (
 5384                "C++".into(),
 5385                LanguageSettingsContent {
 5386                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5387                    preferred_line_length: Some(40),
 5388                    ..Default::default()
 5389                },
 5390            ),
 5391            (
 5392                "Python".into(),
 5393                LanguageSettingsContent {
 5394                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5395                    preferred_line_length: Some(40),
 5396                    ..Default::default()
 5397                },
 5398            ),
 5399            (
 5400                "Rust".into(),
 5401                LanguageSettingsContent {
 5402                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5403                    preferred_line_length: Some(40),
 5404                    ..Default::default()
 5405                },
 5406            ),
 5407        ])
 5408    });
 5409
 5410    let mut cx = EditorTestContext::new(cx).await;
 5411
 5412    let cpp_language = Arc::new(Language::new(
 5413        LanguageConfig {
 5414            name: "C++".into(),
 5415            line_comments: vec!["// ".into()],
 5416            ..LanguageConfig::default()
 5417        },
 5418        None,
 5419    ));
 5420    let python_language = Arc::new(Language::new(
 5421        LanguageConfig {
 5422            name: "Python".into(),
 5423            line_comments: vec!["# ".into()],
 5424            ..LanguageConfig::default()
 5425        },
 5426        None,
 5427    ));
 5428    let markdown_language = Arc::new(Language::new(
 5429        LanguageConfig {
 5430            name: "Markdown".into(),
 5431            rewrap_prefixes: vec![
 5432                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5433                regex::Regex::new("[-*+]\\s+").unwrap(),
 5434            ],
 5435            ..LanguageConfig::default()
 5436        },
 5437        None,
 5438    ));
 5439    let rust_language = Arc::new(Language::new(
 5440        LanguageConfig {
 5441            name: "Rust".into(),
 5442            line_comments: vec!["// ".into(), "/// ".into()],
 5443            ..LanguageConfig::default()
 5444        },
 5445        Some(tree_sitter_rust::LANGUAGE.into()),
 5446    ));
 5447
 5448    let plaintext_language = Arc::new(Language::new(
 5449        LanguageConfig {
 5450            name: "Plain Text".into(),
 5451            ..LanguageConfig::default()
 5452        },
 5453        None,
 5454    ));
 5455
 5456    // Test basic rewrapping of a long line with a cursor
 5457    assert_rewrap(
 5458        indoc! {"
 5459            // ˇThis is a long comment that needs to be wrapped.
 5460        "},
 5461        indoc! {"
 5462            // ˇThis is a long comment that needs to
 5463            // be wrapped.
 5464        "},
 5465        cpp_language.clone(),
 5466        &mut cx,
 5467    );
 5468
 5469    // Test rewrapping a full selection
 5470    assert_rewrap(
 5471        indoc! {"
 5472            «// This selected long comment needs to be wrapped.ˇ»"
 5473        },
 5474        indoc! {"
 5475            «// This selected long comment needs to
 5476            // be wrapped.ˇ»"
 5477        },
 5478        cpp_language.clone(),
 5479        &mut cx,
 5480    );
 5481
 5482    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5483    assert_rewrap(
 5484        indoc! {"
 5485            // ˇThis is the first line.
 5486            // Thisˇ is the second line.
 5487            // This is the thirdˇ line, all part of one paragraph.
 5488         "},
 5489        indoc! {"
 5490            // ˇThis is the first line. Thisˇ is the
 5491            // second line. This is the thirdˇ line,
 5492            // all part of one paragraph.
 5493         "},
 5494        cpp_language.clone(),
 5495        &mut cx,
 5496    );
 5497
 5498    // Test multiple cursors in different paragraphs trigger separate rewraps
 5499    assert_rewrap(
 5500        indoc! {"
 5501            // ˇThis is the first paragraph, first line.
 5502            // ˇThis is the first paragraph, second line.
 5503
 5504            // ˇThis is the second paragraph, first line.
 5505            // ˇThis is the second paragraph, second line.
 5506        "},
 5507        indoc! {"
 5508            // ˇThis is the first paragraph, first
 5509            // line. ˇThis is the first paragraph,
 5510            // second line.
 5511
 5512            // ˇThis is the second paragraph, first
 5513            // line. ˇThis is the second paragraph,
 5514            // second line.
 5515        "},
 5516        cpp_language.clone(),
 5517        &mut cx,
 5518    );
 5519
 5520    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5521    assert_rewrap(
 5522        indoc! {"
 5523            «// A regular long long comment to be wrapped.
 5524            /// A documentation long comment to be wrapped.ˇ»
 5525          "},
 5526        indoc! {"
 5527            «// A regular long long comment to be
 5528            // wrapped.
 5529            /// A documentation long comment to be
 5530            /// wrapped.ˇ»
 5531          "},
 5532        rust_language.clone(),
 5533        &mut cx,
 5534    );
 5535
 5536    // Test that change in indentation level trigger seperate rewraps
 5537    assert_rewrap(
 5538        indoc! {"
 5539            fn foo() {
 5540                «// This is a long comment at the base indent.
 5541                    // This is a long comment at the next indent.ˇ»
 5542            }
 5543        "},
 5544        indoc! {"
 5545            fn foo() {
 5546                «// This is a long comment at the
 5547                // base indent.
 5548                    // This is a long comment at the
 5549                    // next indent.ˇ»
 5550            }
 5551        "},
 5552        rust_language.clone(),
 5553        &mut cx,
 5554    );
 5555
 5556    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5557    assert_rewrap(
 5558        indoc! {"
 5559            # ˇThis is a long comment using a pound sign.
 5560        "},
 5561        indoc! {"
 5562            # ˇThis is a long comment using a pound
 5563            # sign.
 5564        "},
 5565        python_language,
 5566        &mut cx,
 5567    );
 5568
 5569    // Test rewrapping only affects comments, not code even when selected
 5570    assert_rewrap(
 5571        indoc! {"
 5572            «/// This doc comment is long and should be wrapped.
 5573            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5574        "},
 5575        indoc! {"
 5576            «/// This doc comment is long and should
 5577            /// be wrapped.
 5578            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5579        "},
 5580        rust_language.clone(),
 5581        &mut cx,
 5582    );
 5583
 5584    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5585    assert_rewrap(
 5586        indoc! {"
 5587            # Header
 5588
 5589            A long long long line of markdown text to wrap.ˇ
 5590         "},
 5591        indoc! {"
 5592            # Header
 5593
 5594            A long long long line of markdown text
 5595            to wrap.ˇ
 5596         "},
 5597        markdown_language.clone(),
 5598        &mut cx,
 5599    );
 5600
 5601    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5602    assert_rewrap(
 5603        indoc! {"
 5604            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5605            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5606            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5607        "},
 5608        indoc! {"
 5609            «1. This is a numbered list item that is
 5610               very long and needs to be wrapped
 5611               properly.
 5612            2. This is a numbered list item that is
 5613               very long and needs to be wrapped
 5614               properly.
 5615            - This is an unordered list item that is
 5616              also very long and should not merge
 5617              with the numbered item.ˇ»
 5618        "},
 5619        markdown_language.clone(),
 5620        &mut cx,
 5621    );
 5622
 5623    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5624    assert_rewrap(
 5625        indoc! {"
 5626            «1. This is a numbered list item that is
 5627            very long and needs to be wrapped
 5628            properly.
 5629            2. This is a numbered list item that is
 5630            very long and needs to be wrapped
 5631            properly.
 5632            - This is an unordered list item that is
 5633            also very long and should not merge with
 5634            the numbered item.ˇ»
 5635        "},
 5636        indoc! {"
 5637            «1. This is a numbered list item that is
 5638               very long and needs to be wrapped
 5639               properly.
 5640            2. This is a numbered list item that is
 5641               very long and needs to be wrapped
 5642               properly.
 5643            - This is an unordered list item that is
 5644              also very long and should not merge
 5645              with the numbered item.ˇ»
 5646        "},
 5647        markdown_language.clone(),
 5648        &mut cx,
 5649    );
 5650
 5651    // Test that rewrapping maintain indents even when they already exists.
 5652    assert_rewrap(
 5653        indoc! {"
 5654            «1. This is a numbered list
 5655               item that is very long and needs to be wrapped properly.
 5656            2. This is a numbered list
 5657               item that is very long and needs to be wrapped properly.
 5658            - This is an unordered list item that is also very long and
 5659              should not merge with the numbered item.ˇ»
 5660        "},
 5661        indoc! {"
 5662            «1. This is a numbered list item that is
 5663               very long and needs to be wrapped
 5664               properly.
 5665            2. This is a numbered list item that is
 5666               very long and needs to be wrapped
 5667               properly.
 5668            - This is an unordered list item that is
 5669              also very long and should not merge
 5670              with the numbered item.ˇ»
 5671        "},
 5672        markdown_language,
 5673        &mut cx,
 5674    );
 5675
 5676    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5677    assert_rewrap(
 5678        indoc! {"
 5679            ˇThis is a very long line of plain text that will be wrapped.
 5680        "},
 5681        indoc! {"
 5682            ˇThis is a very long line of plain text
 5683            that will be wrapped.
 5684        "},
 5685        plaintext_language.clone(),
 5686        &mut cx,
 5687    );
 5688
 5689    // Test that non-commented code acts as a paragraph boundary within a selection
 5690    assert_rewrap(
 5691        indoc! {"
 5692               «// This is the first long comment block to be wrapped.
 5693               fn my_func(a: u32);
 5694               // This is the second long comment block to be wrapped.ˇ»
 5695           "},
 5696        indoc! {"
 5697               «// This is the first long comment block
 5698               // to be wrapped.
 5699               fn my_func(a: u32);
 5700               // This is the second long comment block
 5701               // to be wrapped.ˇ»
 5702           "},
 5703        rust_language,
 5704        &mut cx,
 5705    );
 5706
 5707    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5708    assert_rewrap(
 5709        indoc! {"
 5710            «ˇThis is a very long line that will be wrapped.
 5711
 5712            This is another paragraph in the same selection.»
 5713
 5714            «\tThis is a very long indented line that will be wrapped.ˇ»
 5715         "},
 5716        indoc! {"
 5717            «ˇThis is a very long line that will be
 5718            wrapped.
 5719
 5720            This is another paragraph in the same
 5721            selection.»
 5722
 5723            «\tThis is a very long indented line
 5724            \tthat will be wrapped.ˇ»
 5725         "},
 5726        plaintext_language,
 5727        &mut cx,
 5728    );
 5729
 5730    // Test that an empty comment line acts as a paragraph boundary
 5731    assert_rewrap(
 5732        indoc! {"
 5733            // ˇThis is a long comment that will be wrapped.
 5734            //
 5735            // And this is another long comment that will also be wrapped.ˇ
 5736         "},
 5737        indoc! {"
 5738            // ˇThis is a long comment that will be
 5739            // wrapped.
 5740            //
 5741            // And this is another long comment that
 5742            // will also be wrapped.ˇ
 5743         "},
 5744        cpp_language,
 5745        &mut cx,
 5746    );
 5747
 5748    #[track_caller]
 5749    fn assert_rewrap(
 5750        unwrapped_text: &str,
 5751        wrapped_text: &str,
 5752        language: Arc<Language>,
 5753        cx: &mut EditorTestContext,
 5754    ) {
 5755        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5756        cx.set_state(unwrapped_text);
 5757        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5758        cx.assert_editor_state(wrapped_text);
 5759    }
 5760}
 5761
 5762#[gpui::test]
 5763async fn test_hard_wrap(cx: &mut TestAppContext) {
 5764    init_test(cx, |_| {});
 5765    let mut cx = EditorTestContext::new(cx).await;
 5766
 5767    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5768    cx.update_editor(|editor, _, cx| {
 5769        editor.set_hard_wrap(Some(14), cx);
 5770    });
 5771
 5772    cx.set_state(indoc!(
 5773        "
 5774        one two three ˇ
 5775        "
 5776    ));
 5777    cx.simulate_input("four");
 5778    cx.run_until_parked();
 5779
 5780    cx.assert_editor_state(indoc!(
 5781        "
 5782        one two three
 5783        fourˇ
 5784        "
 5785    ));
 5786
 5787    cx.update_editor(|editor, window, cx| {
 5788        editor.newline(&Default::default(), window, cx);
 5789    });
 5790    cx.run_until_parked();
 5791    cx.assert_editor_state(indoc!(
 5792        "
 5793        one two three
 5794        four
 5795        ˇ
 5796        "
 5797    ));
 5798
 5799    cx.simulate_input("five");
 5800    cx.run_until_parked();
 5801    cx.assert_editor_state(indoc!(
 5802        "
 5803        one two three
 5804        four
 5805        fiveˇ
 5806        "
 5807    ));
 5808
 5809    cx.update_editor(|editor, window, cx| {
 5810        editor.newline(&Default::default(), window, cx);
 5811    });
 5812    cx.run_until_parked();
 5813    cx.simulate_input("# ");
 5814    cx.run_until_parked();
 5815    cx.assert_editor_state(indoc!(
 5816        "
 5817        one two three
 5818        four
 5819        five
 5820        # ˇ
 5821        "
 5822    ));
 5823
 5824    cx.update_editor(|editor, window, cx| {
 5825        editor.newline(&Default::default(), window, cx);
 5826    });
 5827    cx.run_until_parked();
 5828    cx.assert_editor_state(indoc!(
 5829        "
 5830        one two three
 5831        four
 5832        five
 5833        #\x20
 5834 5835        "
 5836    ));
 5837
 5838    cx.simulate_input(" 6");
 5839    cx.run_until_parked();
 5840    cx.assert_editor_state(indoc!(
 5841        "
 5842        one two three
 5843        four
 5844        five
 5845        #
 5846        # 6ˇ
 5847        "
 5848    ));
 5849}
 5850
 5851#[gpui::test]
 5852async fn test_clipboard(cx: &mut TestAppContext) {
 5853    init_test(cx, |_| {});
 5854
 5855    let mut cx = EditorTestContext::new(cx).await;
 5856
 5857    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5858    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5859    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5860
 5861    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5862    cx.set_state("two ˇfour ˇsix ˇ");
 5863    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5864    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5865
 5866    // Paste again but with only two cursors. Since the number of cursors doesn't
 5867    // match the number of slices in the clipboard, the entire clipboard text
 5868    // is pasted at each cursor.
 5869    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5870    cx.update_editor(|e, window, cx| {
 5871        e.handle_input("( ", window, cx);
 5872        e.paste(&Paste, window, cx);
 5873        e.handle_input(") ", window, cx);
 5874    });
 5875    cx.assert_editor_state(
 5876        &([
 5877            "( one✅ ",
 5878            "three ",
 5879            "five ) ˇtwo one✅ four three six five ( one✅ ",
 5880            "three ",
 5881            "five ) ˇ",
 5882        ]
 5883        .join("\n")),
 5884    );
 5885
 5886    // Cut with three selections, one of which is full-line.
 5887    cx.set_state(indoc! {"
 5888        1«2ˇ»3
 5889        4ˇ567
 5890        «8ˇ»9"});
 5891    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5892    cx.assert_editor_state(indoc! {"
 5893        1ˇ3
 5894        ˇ9"});
 5895
 5896    // Paste with three selections, noticing how the copied selection that was full-line
 5897    // gets inserted before the second cursor.
 5898    cx.set_state(indoc! {"
 5899        1ˇ3
 5900 5901        «oˇ»ne"});
 5902    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5903    cx.assert_editor_state(indoc! {"
 5904        12ˇ3
 5905        4567
 5906 5907        8ˇne"});
 5908
 5909    // Copy with a single cursor only, which writes the whole line into the clipboard.
 5910    cx.set_state(indoc! {"
 5911        The quick brown
 5912        fox juˇmps over
 5913        the lazy dog"});
 5914    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5915    assert_eq!(
 5916        cx.read_from_clipboard()
 5917            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5918        Some("fox jumps over\n".to_string())
 5919    );
 5920
 5921    // Paste with three selections, noticing how the copied full-line selection is inserted
 5922    // before the empty selections but replaces the selection that is non-empty.
 5923    cx.set_state(indoc! {"
 5924        Tˇhe quick brown
 5925        «foˇ»x jumps over
 5926        tˇhe lazy dog"});
 5927    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5928    cx.assert_editor_state(indoc! {"
 5929        fox jumps over
 5930        Tˇhe quick brown
 5931        fox jumps over
 5932        ˇx jumps over
 5933        fox jumps over
 5934        tˇhe lazy dog"});
 5935}
 5936
 5937#[gpui::test]
 5938async fn test_copy_trim(cx: &mut TestAppContext) {
 5939    init_test(cx, |_| {});
 5940
 5941    let mut cx = EditorTestContext::new(cx).await;
 5942    cx.set_state(
 5943        r#"            «for selection in selections.iter() {
 5944            let mut start = selection.start;
 5945            let mut end = selection.end;
 5946            let is_entire_line = selection.is_empty();
 5947            if is_entire_line {
 5948                start = Point::new(start.row, 0);ˇ»
 5949                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5950            }
 5951        "#,
 5952    );
 5953    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5954    assert_eq!(
 5955        cx.read_from_clipboard()
 5956            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5957        Some(
 5958            "for selection in selections.iter() {
 5959            let mut start = selection.start;
 5960            let mut end = selection.end;
 5961            let is_entire_line = selection.is_empty();
 5962            if is_entire_line {
 5963                start = Point::new(start.row, 0);"
 5964                .to_string()
 5965        ),
 5966        "Regular copying preserves all indentation selected",
 5967    );
 5968    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5969    assert_eq!(
 5970        cx.read_from_clipboard()
 5971            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5972        Some(
 5973            "for selection in selections.iter() {
 5974let mut start = selection.start;
 5975let mut end = selection.end;
 5976let is_entire_line = selection.is_empty();
 5977if is_entire_line {
 5978    start = Point::new(start.row, 0);"
 5979                .to_string()
 5980        ),
 5981        "Copying with stripping should strip all leading whitespaces"
 5982    );
 5983
 5984    cx.set_state(
 5985        r#"       «     for selection in selections.iter() {
 5986            let mut start = selection.start;
 5987            let mut end = selection.end;
 5988            let is_entire_line = selection.is_empty();
 5989            if is_entire_line {
 5990                start = Point::new(start.row, 0);ˇ»
 5991                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5992            }
 5993        "#,
 5994    );
 5995    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5996    assert_eq!(
 5997        cx.read_from_clipboard()
 5998            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5999        Some(
 6000            "     for selection in selections.iter() {
 6001            let mut start = selection.start;
 6002            let mut end = selection.end;
 6003            let is_entire_line = selection.is_empty();
 6004            if is_entire_line {
 6005                start = Point::new(start.row, 0);"
 6006                .to_string()
 6007        ),
 6008        "Regular copying preserves all indentation selected",
 6009    );
 6010    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6011    assert_eq!(
 6012        cx.read_from_clipboard()
 6013            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6014        Some(
 6015            "for selection in selections.iter() {
 6016let mut start = selection.start;
 6017let mut end = selection.end;
 6018let is_entire_line = selection.is_empty();
 6019if is_entire_line {
 6020    start = Point::new(start.row, 0);"
 6021                .to_string()
 6022        ),
 6023        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6024    );
 6025
 6026    cx.set_state(
 6027        r#"       «ˇ     for selection in selections.iter() {
 6028            let mut start = selection.start;
 6029            let mut end = selection.end;
 6030            let is_entire_line = selection.is_empty();
 6031            if is_entire_line {
 6032                start = Point::new(start.row, 0);»
 6033                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6034            }
 6035        "#,
 6036    );
 6037    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6038    assert_eq!(
 6039        cx.read_from_clipboard()
 6040            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6041        Some(
 6042            "     for selection in selections.iter() {
 6043            let mut start = selection.start;
 6044            let mut end = selection.end;
 6045            let is_entire_line = selection.is_empty();
 6046            if is_entire_line {
 6047                start = Point::new(start.row, 0);"
 6048                .to_string()
 6049        ),
 6050        "Regular copying for reverse selection works the same",
 6051    );
 6052    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6053    assert_eq!(
 6054        cx.read_from_clipboard()
 6055            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6056        Some(
 6057            "for selection in selections.iter() {
 6058let mut start = selection.start;
 6059let mut end = selection.end;
 6060let is_entire_line = selection.is_empty();
 6061if is_entire_line {
 6062    start = Point::new(start.row, 0);"
 6063                .to_string()
 6064        ),
 6065        "Copying with stripping for reverse selection works the same"
 6066    );
 6067
 6068    cx.set_state(
 6069        r#"            for selection «in selections.iter() {
 6070            let mut start = selection.start;
 6071            let mut end = selection.end;
 6072            let is_entire_line = selection.is_empty();
 6073            if is_entire_line {
 6074                start = Point::new(start.row, 0);ˇ»
 6075                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6076            }
 6077        "#,
 6078    );
 6079    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6080    assert_eq!(
 6081        cx.read_from_clipboard()
 6082            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6083        Some(
 6084            "in selections.iter() {
 6085            let mut start = selection.start;
 6086            let mut end = selection.end;
 6087            let is_entire_line = selection.is_empty();
 6088            if is_entire_line {
 6089                start = Point::new(start.row, 0);"
 6090                .to_string()
 6091        ),
 6092        "When selecting past the indent, the copying works as usual",
 6093    );
 6094    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6095    assert_eq!(
 6096        cx.read_from_clipboard()
 6097            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6098        Some(
 6099            "in selections.iter() {
 6100            let mut start = selection.start;
 6101            let mut end = selection.end;
 6102            let is_entire_line = selection.is_empty();
 6103            if is_entire_line {
 6104                start = Point::new(start.row, 0);"
 6105                .to_string()
 6106        ),
 6107        "When selecting past the indent, nothing is trimmed"
 6108    );
 6109
 6110    cx.set_state(
 6111        r#"            «for selection in selections.iter() {
 6112            let mut start = selection.start;
 6113
 6114            let mut end = selection.end;
 6115            let is_entire_line = selection.is_empty();
 6116            if is_entire_line {
 6117                start = Point::new(start.row, 0);
 6118ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6119            }
 6120        "#,
 6121    );
 6122    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6123    assert_eq!(
 6124        cx.read_from_clipboard()
 6125            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6126        Some(
 6127            "for selection in selections.iter() {
 6128let mut start = selection.start;
 6129
 6130let mut end = selection.end;
 6131let is_entire_line = selection.is_empty();
 6132if is_entire_line {
 6133    start = Point::new(start.row, 0);
 6134"
 6135            .to_string()
 6136        ),
 6137        "Copying with stripping should ignore empty lines"
 6138    );
 6139}
 6140
 6141#[gpui::test]
 6142async fn test_paste_multiline(cx: &mut TestAppContext) {
 6143    init_test(cx, |_| {});
 6144
 6145    let mut cx = EditorTestContext::new(cx).await;
 6146    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6147
 6148    // Cut an indented block, without the leading whitespace.
 6149    cx.set_state(indoc! {"
 6150        const a: B = (
 6151            c(),
 6152            «d(
 6153                e,
 6154                f
 6155            )ˇ»
 6156        );
 6157    "});
 6158    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6159    cx.assert_editor_state(indoc! {"
 6160        const a: B = (
 6161            c(),
 6162            ˇ
 6163        );
 6164    "});
 6165
 6166    // Paste it at the same position.
 6167    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6168    cx.assert_editor_state(indoc! {"
 6169        const a: B = (
 6170            c(),
 6171            d(
 6172                e,
 6173                f
 6174 6175        );
 6176    "});
 6177
 6178    // Paste it at a line with a lower indent level.
 6179    cx.set_state(indoc! {"
 6180        ˇ
 6181        const a: B = (
 6182            c(),
 6183        );
 6184    "});
 6185    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6186    cx.assert_editor_state(indoc! {"
 6187        d(
 6188            e,
 6189            f
 6190 6191        const a: B = (
 6192            c(),
 6193        );
 6194    "});
 6195
 6196    // Cut an indented block, with the leading whitespace.
 6197    cx.set_state(indoc! {"
 6198        const a: B = (
 6199            c(),
 6200        «    d(
 6201                e,
 6202                f
 6203            )
 6204        ˇ»);
 6205    "});
 6206    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6207    cx.assert_editor_state(indoc! {"
 6208        const a: B = (
 6209            c(),
 6210        ˇ);
 6211    "});
 6212
 6213    // Paste it at the same position.
 6214    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6215    cx.assert_editor_state(indoc! {"
 6216        const a: B = (
 6217            c(),
 6218            d(
 6219                e,
 6220                f
 6221            )
 6222        ˇ);
 6223    "});
 6224
 6225    // Paste it at a line with a higher indent level.
 6226    cx.set_state(indoc! {"
 6227        const a: B = (
 6228            c(),
 6229            d(
 6230                e,
 6231 6232            )
 6233        );
 6234    "});
 6235    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6236    cx.assert_editor_state(indoc! {"
 6237        const a: B = (
 6238            c(),
 6239            d(
 6240                e,
 6241                f    d(
 6242                    e,
 6243                    f
 6244                )
 6245        ˇ
 6246            )
 6247        );
 6248    "});
 6249
 6250    // Copy an indented block, starting mid-line
 6251    cx.set_state(indoc! {"
 6252        const a: B = (
 6253            c(),
 6254            somethin«g(
 6255                e,
 6256                f
 6257            )ˇ»
 6258        );
 6259    "});
 6260    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6261
 6262    // Paste it on a line with a lower indent level
 6263    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6264    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6265    cx.assert_editor_state(indoc! {"
 6266        const a: B = (
 6267            c(),
 6268            something(
 6269                e,
 6270                f
 6271            )
 6272        );
 6273        g(
 6274            e,
 6275            f
 6276"});
 6277}
 6278
 6279#[gpui::test]
 6280async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6281    init_test(cx, |_| {});
 6282
 6283    cx.write_to_clipboard(ClipboardItem::new_string(
 6284        "    d(\n        e\n    );\n".into(),
 6285    ));
 6286
 6287    let mut cx = EditorTestContext::new(cx).await;
 6288    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6289
 6290    cx.set_state(indoc! {"
 6291        fn a() {
 6292            b();
 6293            if c() {
 6294                ˇ
 6295            }
 6296        }
 6297    "});
 6298
 6299    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6300    cx.assert_editor_state(indoc! {"
 6301        fn a() {
 6302            b();
 6303            if c() {
 6304                d(
 6305                    e
 6306                );
 6307        ˇ
 6308            }
 6309        }
 6310    "});
 6311
 6312    cx.set_state(indoc! {"
 6313        fn a() {
 6314            b();
 6315            ˇ
 6316        }
 6317    "});
 6318
 6319    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6320    cx.assert_editor_state(indoc! {"
 6321        fn a() {
 6322            b();
 6323            d(
 6324                e
 6325            );
 6326        ˇ
 6327        }
 6328    "});
 6329}
 6330
 6331#[gpui::test]
 6332fn test_select_all(cx: &mut TestAppContext) {
 6333    init_test(cx, |_| {});
 6334
 6335    let editor = cx.add_window(|window, cx| {
 6336        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6337        build_editor(buffer, window, cx)
 6338    });
 6339    _ = editor.update(cx, |editor, window, cx| {
 6340        editor.select_all(&SelectAll, window, cx);
 6341        assert_eq!(
 6342            editor.selections.display_ranges(cx),
 6343            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6344        );
 6345    });
 6346}
 6347
 6348#[gpui::test]
 6349fn test_select_line(cx: &mut TestAppContext) {
 6350    init_test(cx, |_| {});
 6351
 6352    let editor = cx.add_window(|window, cx| {
 6353        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6354        build_editor(buffer, window, cx)
 6355    });
 6356    _ = editor.update(cx, |editor, window, cx| {
 6357        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6358            s.select_display_ranges([
 6359                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6360                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6361                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6362                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6363            ])
 6364        });
 6365        editor.select_line(&SelectLine, window, cx);
 6366        assert_eq!(
 6367            editor.selections.display_ranges(cx),
 6368            vec![
 6369                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6370                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6371            ]
 6372        );
 6373    });
 6374
 6375    _ = editor.update(cx, |editor, window, cx| {
 6376        editor.select_line(&SelectLine, window, cx);
 6377        assert_eq!(
 6378            editor.selections.display_ranges(cx),
 6379            vec![
 6380                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6381                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6382            ]
 6383        );
 6384    });
 6385
 6386    _ = editor.update(cx, |editor, window, cx| {
 6387        editor.select_line(&SelectLine, window, cx);
 6388        assert_eq!(
 6389            editor.selections.display_ranges(cx),
 6390            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6391        );
 6392    });
 6393}
 6394
 6395#[gpui::test]
 6396async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6397    init_test(cx, |_| {});
 6398    let mut cx = EditorTestContext::new(cx).await;
 6399
 6400    #[track_caller]
 6401    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6402        cx.set_state(initial_state);
 6403        cx.update_editor(|e, window, cx| {
 6404            e.split_selection_into_lines(&Default::default(), window, cx)
 6405        });
 6406        cx.assert_editor_state(expected_state);
 6407    }
 6408
 6409    // Selection starts and ends at the middle of lines, left-to-right
 6410    test(
 6411        &mut cx,
 6412        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6413        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6414    );
 6415    // Same thing, right-to-left
 6416    test(
 6417        &mut cx,
 6418        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6419        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6420    );
 6421
 6422    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6423    test(
 6424        &mut cx,
 6425        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6426        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6427    );
 6428    // Same thing, right-to-left
 6429    test(
 6430        &mut cx,
 6431        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6432        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6433    );
 6434
 6435    // Whole buffer, left-to-right, last line ends with newline
 6436    test(
 6437        &mut cx,
 6438        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6439        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6440    );
 6441    // Same thing, right-to-left
 6442    test(
 6443        &mut cx,
 6444        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6445        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6446    );
 6447
 6448    // Starts at the end of a line, ends at the start of another
 6449    test(
 6450        &mut cx,
 6451        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6452        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6453    );
 6454}
 6455
 6456#[gpui::test]
 6457async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6458    init_test(cx, |_| {});
 6459
 6460    let editor = cx.add_window(|window, cx| {
 6461        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6462        build_editor(buffer, window, cx)
 6463    });
 6464
 6465    // setup
 6466    _ = editor.update(cx, |editor, window, cx| {
 6467        editor.fold_creases(
 6468            vec![
 6469                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6470                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6471                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6472            ],
 6473            true,
 6474            window,
 6475            cx,
 6476        );
 6477        assert_eq!(
 6478            editor.display_text(cx),
 6479            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6480        );
 6481    });
 6482
 6483    _ = editor.update(cx, |editor, window, cx| {
 6484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6485            s.select_display_ranges([
 6486                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6487                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6488                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6489                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6490            ])
 6491        });
 6492        editor.split_selection_into_lines(&Default::default(), window, cx);
 6493        assert_eq!(
 6494            editor.display_text(cx),
 6495            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6496        );
 6497    });
 6498    EditorTestContext::for_editor(editor, cx)
 6499        .await
 6500        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6501
 6502    _ = editor.update(cx, |editor, window, cx| {
 6503        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6504            s.select_display_ranges([
 6505                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6506            ])
 6507        });
 6508        editor.split_selection_into_lines(&Default::default(), window, cx);
 6509        assert_eq!(
 6510            editor.display_text(cx),
 6511            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6512        );
 6513        assert_eq!(
 6514            editor.selections.display_ranges(cx),
 6515            [
 6516                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6517                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6518                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6519                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6520                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6521                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6522                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6523            ]
 6524        );
 6525    });
 6526    EditorTestContext::for_editor(editor, cx)
 6527        .await
 6528        .assert_editor_state(
 6529            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6530        );
 6531}
 6532
 6533#[gpui::test]
 6534async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6535    init_test(cx, |_| {});
 6536
 6537    let mut cx = EditorTestContext::new(cx).await;
 6538
 6539    cx.set_state(indoc!(
 6540        r#"abc
 6541           defˇghi
 6542
 6543           jk
 6544           nlmo
 6545           "#
 6546    ));
 6547
 6548    cx.update_editor(|editor, window, cx| {
 6549        editor.add_selection_above(&Default::default(), window, cx);
 6550    });
 6551
 6552    cx.assert_editor_state(indoc!(
 6553        r#"abcˇ
 6554           defˇghi
 6555
 6556           jk
 6557           nlmo
 6558           "#
 6559    ));
 6560
 6561    cx.update_editor(|editor, window, cx| {
 6562        editor.add_selection_above(&Default::default(), window, cx);
 6563    });
 6564
 6565    cx.assert_editor_state(indoc!(
 6566        r#"abcˇ
 6567            defˇghi
 6568
 6569            jk
 6570            nlmo
 6571            "#
 6572    ));
 6573
 6574    cx.update_editor(|editor, window, cx| {
 6575        editor.add_selection_below(&Default::default(), window, cx);
 6576    });
 6577
 6578    cx.assert_editor_state(indoc!(
 6579        r#"abc
 6580           defˇghi
 6581
 6582           jk
 6583           nlmo
 6584           "#
 6585    ));
 6586
 6587    cx.update_editor(|editor, window, cx| {
 6588        editor.undo_selection(&Default::default(), window, cx);
 6589    });
 6590
 6591    cx.assert_editor_state(indoc!(
 6592        r#"abcˇ
 6593           defˇghi
 6594
 6595           jk
 6596           nlmo
 6597           "#
 6598    ));
 6599
 6600    cx.update_editor(|editor, window, cx| {
 6601        editor.redo_selection(&Default::default(), window, cx);
 6602    });
 6603
 6604    cx.assert_editor_state(indoc!(
 6605        r#"abc
 6606           defˇghi
 6607
 6608           jk
 6609           nlmo
 6610           "#
 6611    ));
 6612
 6613    cx.update_editor(|editor, window, cx| {
 6614        editor.add_selection_below(&Default::default(), window, cx);
 6615    });
 6616
 6617    cx.assert_editor_state(indoc!(
 6618        r#"abc
 6619           defˇghi
 6620           ˇ
 6621           jk
 6622           nlmo
 6623           "#
 6624    ));
 6625
 6626    cx.update_editor(|editor, window, cx| {
 6627        editor.add_selection_below(&Default::default(), window, cx);
 6628    });
 6629
 6630    cx.assert_editor_state(indoc!(
 6631        r#"abc
 6632           defˇghi
 6633           ˇ
 6634           jkˇ
 6635           nlmo
 6636           "#
 6637    ));
 6638
 6639    cx.update_editor(|editor, window, cx| {
 6640        editor.add_selection_below(&Default::default(), window, cx);
 6641    });
 6642
 6643    cx.assert_editor_state(indoc!(
 6644        r#"abc
 6645           defˇghi
 6646           ˇ
 6647           jkˇ
 6648           nlmˇo
 6649           "#
 6650    ));
 6651
 6652    cx.update_editor(|editor, window, cx| {
 6653        editor.add_selection_below(&Default::default(), window, cx);
 6654    });
 6655
 6656    cx.assert_editor_state(indoc!(
 6657        r#"abc
 6658           defˇghi
 6659           ˇ
 6660           jkˇ
 6661           nlmˇo
 6662           ˇ"#
 6663    ));
 6664
 6665    // change selections
 6666    cx.set_state(indoc!(
 6667        r#"abc
 6668           def«ˇg»hi
 6669
 6670           jk
 6671           nlmo
 6672           "#
 6673    ));
 6674
 6675    cx.update_editor(|editor, window, cx| {
 6676        editor.add_selection_below(&Default::default(), window, cx);
 6677    });
 6678
 6679    cx.assert_editor_state(indoc!(
 6680        r#"abc
 6681           def«ˇg»hi
 6682
 6683           jk
 6684           nlm«ˇo»
 6685           "#
 6686    ));
 6687
 6688    cx.update_editor(|editor, window, cx| {
 6689        editor.add_selection_below(&Default::default(), window, cx);
 6690    });
 6691
 6692    cx.assert_editor_state(indoc!(
 6693        r#"abc
 6694           def«ˇg»hi
 6695
 6696           jk
 6697           nlm«ˇo»
 6698           "#
 6699    ));
 6700
 6701    cx.update_editor(|editor, window, cx| {
 6702        editor.add_selection_above(&Default::default(), window, cx);
 6703    });
 6704
 6705    cx.assert_editor_state(indoc!(
 6706        r#"abc
 6707           def«ˇg»hi
 6708
 6709           jk
 6710           nlmo
 6711           "#
 6712    ));
 6713
 6714    cx.update_editor(|editor, window, cx| {
 6715        editor.add_selection_above(&Default::default(), window, cx);
 6716    });
 6717
 6718    cx.assert_editor_state(indoc!(
 6719        r#"abc
 6720           def«ˇg»hi
 6721
 6722           jk
 6723           nlmo
 6724           "#
 6725    ));
 6726
 6727    // Change selections again
 6728    cx.set_state(indoc!(
 6729        r#"a«bc
 6730           defgˇ»hi
 6731
 6732           jk
 6733           nlmo
 6734           "#
 6735    ));
 6736
 6737    cx.update_editor(|editor, window, cx| {
 6738        editor.add_selection_below(&Default::default(), window, cx);
 6739    });
 6740
 6741    cx.assert_editor_state(indoc!(
 6742        r#"a«bcˇ»
 6743           d«efgˇ»hi
 6744
 6745           j«kˇ»
 6746           nlmo
 6747           "#
 6748    ));
 6749
 6750    cx.update_editor(|editor, window, cx| {
 6751        editor.add_selection_below(&Default::default(), window, cx);
 6752    });
 6753    cx.assert_editor_state(indoc!(
 6754        r#"a«bcˇ»
 6755           d«efgˇ»hi
 6756
 6757           j«kˇ»
 6758           n«lmoˇ»
 6759           "#
 6760    ));
 6761    cx.update_editor(|editor, window, cx| {
 6762        editor.add_selection_above(&Default::default(), window, cx);
 6763    });
 6764
 6765    cx.assert_editor_state(indoc!(
 6766        r#"a«bcˇ»
 6767           d«efgˇ»hi
 6768
 6769           j«kˇ»
 6770           nlmo
 6771           "#
 6772    ));
 6773
 6774    // Change selections again
 6775    cx.set_state(indoc!(
 6776        r#"abc
 6777           d«ˇefghi
 6778
 6779           jk
 6780           nlm»o
 6781           "#
 6782    ));
 6783
 6784    cx.update_editor(|editor, window, cx| {
 6785        editor.add_selection_above(&Default::default(), window, cx);
 6786    });
 6787
 6788    cx.assert_editor_state(indoc!(
 6789        r#"a«ˇbc»
 6790           d«ˇef»ghi
 6791
 6792           j«ˇk»
 6793           n«ˇlm»o
 6794           "#
 6795    ));
 6796
 6797    cx.update_editor(|editor, window, cx| {
 6798        editor.add_selection_below(&Default::default(), window, cx);
 6799    });
 6800
 6801    cx.assert_editor_state(indoc!(
 6802        r#"abc
 6803           d«ˇef»ghi
 6804
 6805           j«ˇk»
 6806           n«ˇlm»o
 6807           "#
 6808    ));
 6809}
 6810
 6811#[gpui::test]
 6812async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6813    init_test(cx, |_| {});
 6814    let mut cx = EditorTestContext::new(cx).await;
 6815
 6816    cx.set_state(indoc!(
 6817        r#"line onˇe
 6818           liˇne two
 6819           line three
 6820           line four"#
 6821    ));
 6822
 6823    cx.update_editor(|editor, window, cx| {
 6824        editor.add_selection_below(&Default::default(), window, cx);
 6825    });
 6826
 6827    // test multiple cursors expand in the same direction
 6828    cx.assert_editor_state(indoc!(
 6829        r#"line onˇe
 6830           liˇne twˇo
 6831           liˇne three
 6832           line four"#
 6833    ));
 6834
 6835    cx.update_editor(|editor, window, cx| {
 6836        editor.add_selection_below(&Default::default(), window, cx);
 6837    });
 6838
 6839    cx.update_editor(|editor, window, cx| {
 6840        editor.add_selection_below(&Default::default(), window, cx);
 6841    });
 6842
 6843    // test multiple cursors expand below overflow
 6844    cx.assert_editor_state(indoc!(
 6845        r#"line onˇe
 6846           liˇne twˇo
 6847           liˇne thˇree
 6848           liˇne foˇur"#
 6849    ));
 6850
 6851    cx.update_editor(|editor, window, cx| {
 6852        editor.add_selection_above(&Default::default(), window, cx);
 6853    });
 6854
 6855    // test multiple cursors retrieves back correctly
 6856    cx.assert_editor_state(indoc!(
 6857        r#"line onˇe
 6858           liˇne twˇo
 6859           liˇne thˇree
 6860           line four"#
 6861    ));
 6862
 6863    cx.update_editor(|editor, window, cx| {
 6864        editor.add_selection_above(&Default::default(), window, cx);
 6865    });
 6866
 6867    cx.update_editor(|editor, window, cx| {
 6868        editor.add_selection_above(&Default::default(), window, cx);
 6869    });
 6870
 6871    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6872    cx.assert_editor_state(indoc!(
 6873        r#"liˇne onˇe
 6874           liˇne two
 6875           line three
 6876           line four"#
 6877    ));
 6878
 6879    cx.update_editor(|editor, window, cx| {
 6880        editor.undo_selection(&Default::default(), window, cx);
 6881    });
 6882
 6883    // test undo
 6884    cx.assert_editor_state(indoc!(
 6885        r#"line onˇe
 6886           liˇne twˇo
 6887           line three
 6888           line four"#
 6889    ));
 6890
 6891    cx.update_editor(|editor, window, cx| {
 6892        editor.redo_selection(&Default::default(), window, cx);
 6893    });
 6894
 6895    // test redo
 6896    cx.assert_editor_state(indoc!(
 6897        r#"liˇne onˇe
 6898           liˇne two
 6899           line three
 6900           line four"#
 6901    ));
 6902
 6903    cx.set_state(indoc!(
 6904        r#"abcd
 6905           ef«ghˇ»
 6906           ijkl
 6907           «mˇ»nop"#
 6908    ));
 6909
 6910    cx.update_editor(|editor, window, cx| {
 6911        editor.add_selection_above(&Default::default(), window, cx);
 6912    });
 6913
 6914    // test multiple selections expand in the same direction
 6915    cx.assert_editor_state(indoc!(
 6916        r#"ab«cdˇ»
 6917           ef«ghˇ»
 6918           «iˇ»jkl
 6919           «mˇ»nop"#
 6920    ));
 6921
 6922    cx.update_editor(|editor, window, cx| {
 6923        editor.add_selection_above(&Default::default(), window, cx);
 6924    });
 6925
 6926    // test multiple selection upward overflow
 6927    cx.assert_editor_state(indoc!(
 6928        r#"ab«cdˇ»
 6929           «eˇ»f«ghˇ»
 6930           «iˇ»jkl
 6931           «mˇ»nop"#
 6932    ));
 6933
 6934    cx.update_editor(|editor, window, cx| {
 6935        editor.add_selection_below(&Default::default(), window, cx);
 6936    });
 6937
 6938    // test multiple selection retrieves back correctly
 6939    cx.assert_editor_state(indoc!(
 6940        r#"abcd
 6941           ef«ghˇ»
 6942           «iˇ»jkl
 6943           «mˇ»nop"#
 6944    ));
 6945
 6946    cx.update_editor(|editor, window, cx| {
 6947        editor.add_selection_below(&Default::default(), window, cx);
 6948    });
 6949
 6950    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 6951    cx.assert_editor_state(indoc!(
 6952        r#"abcd
 6953           ef«ghˇ»
 6954           ij«klˇ»
 6955           «mˇ»nop"#
 6956    ));
 6957
 6958    cx.update_editor(|editor, window, cx| {
 6959        editor.undo_selection(&Default::default(), window, cx);
 6960    });
 6961
 6962    // test undo
 6963    cx.assert_editor_state(indoc!(
 6964        r#"abcd
 6965           ef«ghˇ»
 6966           «iˇ»jkl
 6967           «mˇ»nop"#
 6968    ));
 6969
 6970    cx.update_editor(|editor, window, cx| {
 6971        editor.redo_selection(&Default::default(), window, cx);
 6972    });
 6973
 6974    // test redo
 6975    cx.assert_editor_state(indoc!(
 6976        r#"abcd
 6977           ef«ghˇ»
 6978           ij«klˇ»
 6979           «mˇ»nop"#
 6980    ));
 6981}
 6982
 6983#[gpui::test]
 6984async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 6985    init_test(cx, |_| {});
 6986    let mut cx = EditorTestContext::new(cx).await;
 6987
 6988    cx.set_state(indoc!(
 6989        r#"line onˇe
 6990           liˇne two
 6991           line three
 6992           line four"#
 6993    ));
 6994
 6995    cx.update_editor(|editor, window, cx| {
 6996        editor.add_selection_below(&Default::default(), window, cx);
 6997        editor.add_selection_below(&Default::default(), window, cx);
 6998        editor.add_selection_below(&Default::default(), window, cx);
 6999    });
 7000
 7001    // initial state with two multi cursor groups
 7002    cx.assert_editor_state(indoc!(
 7003        r#"line onˇe
 7004           liˇne twˇo
 7005           liˇne thˇree
 7006           liˇne foˇur"#
 7007    ));
 7008
 7009    // add single cursor in middle - simulate opt click
 7010    cx.update_editor(|editor, window, cx| {
 7011        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7012        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7013        editor.end_selection(window, cx);
 7014    });
 7015
 7016    cx.assert_editor_state(indoc!(
 7017        r#"line onˇe
 7018           liˇne twˇo
 7019           liˇneˇ thˇree
 7020           liˇne foˇur"#
 7021    ));
 7022
 7023    cx.update_editor(|editor, window, cx| {
 7024        editor.add_selection_above(&Default::default(), window, cx);
 7025    });
 7026
 7027    // test new added selection expands above and existing selection shrinks
 7028    cx.assert_editor_state(indoc!(
 7029        r#"line onˇe
 7030           liˇneˇ twˇo
 7031           liˇneˇ thˇree
 7032           line four"#
 7033    ));
 7034
 7035    cx.update_editor(|editor, window, cx| {
 7036        editor.add_selection_above(&Default::default(), window, cx);
 7037    });
 7038
 7039    // test new added selection expands above and existing selection shrinks
 7040    cx.assert_editor_state(indoc!(
 7041        r#"lineˇ onˇe
 7042           liˇneˇ twˇo
 7043           lineˇ three
 7044           line four"#
 7045    ));
 7046
 7047    // intial state with two selection groups
 7048    cx.set_state(indoc!(
 7049        r#"abcd
 7050           ef«ghˇ»
 7051           ijkl
 7052           «mˇ»nop"#
 7053    ));
 7054
 7055    cx.update_editor(|editor, window, cx| {
 7056        editor.add_selection_above(&Default::default(), window, cx);
 7057        editor.add_selection_above(&Default::default(), window, cx);
 7058    });
 7059
 7060    cx.assert_editor_state(indoc!(
 7061        r#"ab«cdˇ»
 7062           «eˇ»f«ghˇ»
 7063           «iˇ»jkl
 7064           «mˇ»nop"#
 7065    ));
 7066
 7067    // add single selection in middle - simulate opt drag
 7068    cx.update_editor(|editor, window, cx| {
 7069        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7070        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7071        editor.update_selection(
 7072            DisplayPoint::new(DisplayRow(2), 4),
 7073            0,
 7074            gpui::Point::<f32>::default(),
 7075            window,
 7076            cx,
 7077        );
 7078        editor.end_selection(window, cx);
 7079    });
 7080
 7081    cx.assert_editor_state(indoc!(
 7082        r#"ab«cdˇ»
 7083           «eˇ»f«ghˇ»
 7084           «iˇ»jk«lˇ»
 7085           «mˇ»nop"#
 7086    ));
 7087
 7088    cx.update_editor(|editor, window, cx| {
 7089        editor.add_selection_below(&Default::default(), window, cx);
 7090    });
 7091
 7092    // test new added selection expands below, others shrinks from above
 7093    cx.assert_editor_state(indoc!(
 7094        r#"abcd
 7095           ef«ghˇ»
 7096           «iˇ»jk«lˇ»
 7097           «mˇ»no«pˇ»"#
 7098    ));
 7099}
 7100
 7101#[gpui::test]
 7102async fn test_select_next(cx: &mut TestAppContext) {
 7103    init_test(cx, |_| {});
 7104
 7105    let mut cx = EditorTestContext::new(cx).await;
 7106    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7107
 7108    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7109        .unwrap();
 7110    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7111
 7112    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7113        .unwrap();
 7114    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7115
 7116    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7117    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7118
 7119    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7120    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7121
 7122    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7123        .unwrap();
 7124    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7125
 7126    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7127        .unwrap();
 7128    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7129
 7130    // Test selection direction should be preserved
 7131    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7132
 7133    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7134        .unwrap();
 7135    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7136}
 7137
 7138#[gpui::test]
 7139async fn test_select_all_matches(cx: &mut TestAppContext) {
 7140    init_test(cx, |_| {});
 7141
 7142    let mut cx = EditorTestContext::new(cx).await;
 7143
 7144    // Test caret-only selections
 7145    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7146    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7147        .unwrap();
 7148    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7149
 7150    // Test left-to-right selections
 7151    cx.set_state("abc\n«abcˇ»\nabc");
 7152    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7153        .unwrap();
 7154    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7155
 7156    // Test right-to-left selections
 7157    cx.set_state("abc\n«ˇabc»\nabc");
 7158    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7159        .unwrap();
 7160    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7161
 7162    // Test selecting whitespace with caret selection
 7163    cx.set_state("abc\nˇ   abc\nabc");
 7164    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7165        .unwrap();
 7166    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7167
 7168    // Test selecting whitespace with left-to-right selection
 7169    cx.set_state("abc\n«ˇ  »abc\nabc");
 7170    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7171        .unwrap();
 7172    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7173
 7174    // Test no matches with right-to-left selection
 7175    cx.set_state("abc\n«  ˇ»abc\nabc");
 7176    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7177        .unwrap();
 7178    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7179
 7180    // Test with a single word and clip_at_line_ends=true (#29823)
 7181    cx.set_state("aˇbc");
 7182    cx.update_editor(|e, window, cx| {
 7183        e.set_clip_at_line_ends(true, cx);
 7184        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7185        e.set_clip_at_line_ends(false, cx);
 7186    });
 7187    cx.assert_editor_state("«abcˇ»");
 7188}
 7189
 7190#[gpui::test]
 7191async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7192    init_test(cx, |_| {});
 7193
 7194    let mut cx = EditorTestContext::new(cx).await;
 7195
 7196    let large_body_1 = "\nd".repeat(200);
 7197    let large_body_2 = "\ne".repeat(200);
 7198
 7199    cx.set_state(&format!(
 7200        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7201    ));
 7202    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7203        let scroll_position = editor.scroll_position(cx);
 7204        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7205        scroll_position
 7206    });
 7207
 7208    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7209        .unwrap();
 7210    cx.assert_editor_state(&format!(
 7211        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7212    ));
 7213    let scroll_position_after_selection =
 7214        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7215    assert_eq!(
 7216        initial_scroll_position, scroll_position_after_selection,
 7217        "Scroll position should not change after selecting all matches"
 7218    );
 7219}
 7220
 7221#[gpui::test]
 7222async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7223    init_test(cx, |_| {});
 7224
 7225    let mut cx = EditorLspTestContext::new_rust(
 7226        lsp::ServerCapabilities {
 7227            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7228            ..Default::default()
 7229        },
 7230        cx,
 7231    )
 7232    .await;
 7233
 7234    cx.set_state(indoc! {"
 7235        line 1
 7236        line 2
 7237        linˇe 3
 7238        line 4
 7239        line 5
 7240    "});
 7241
 7242    // Make an edit
 7243    cx.update_editor(|editor, window, cx| {
 7244        editor.handle_input("X", window, cx);
 7245    });
 7246
 7247    // Move cursor to a different position
 7248    cx.update_editor(|editor, window, cx| {
 7249        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7250            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7251        });
 7252    });
 7253
 7254    cx.assert_editor_state(indoc! {"
 7255        line 1
 7256        line 2
 7257        linXe 3
 7258        line 4
 7259        liˇne 5
 7260    "});
 7261
 7262    cx.lsp
 7263        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7264            Ok(Some(vec![lsp::TextEdit::new(
 7265                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7266                "PREFIX ".to_string(),
 7267            )]))
 7268        });
 7269
 7270    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7271        .unwrap()
 7272        .await
 7273        .unwrap();
 7274
 7275    cx.assert_editor_state(indoc! {"
 7276        PREFIX line 1
 7277        line 2
 7278        linXe 3
 7279        line 4
 7280        liˇne 5
 7281    "});
 7282
 7283    // Undo formatting
 7284    cx.update_editor(|editor, window, cx| {
 7285        editor.undo(&Default::default(), window, cx);
 7286    });
 7287
 7288    // Verify cursor moved back to position after edit
 7289    cx.assert_editor_state(indoc! {"
 7290        line 1
 7291        line 2
 7292        linXˇe 3
 7293        line 4
 7294        line 5
 7295    "});
 7296}
 7297
 7298#[gpui::test]
 7299async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7300    init_test(cx, |_| {});
 7301
 7302    let mut cx = EditorTestContext::new(cx).await;
 7303
 7304    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7305    cx.update_editor(|editor, window, cx| {
 7306        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7307    });
 7308
 7309    cx.set_state(indoc! {"
 7310        line 1
 7311        line 2
 7312        linˇe 3
 7313        line 4
 7314        line 5
 7315        line 6
 7316        line 7
 7317        line 8
 7318        line 9
 7319        line 10
 7320    "});
 7321
 7322    let snapshot = cx.buffer_snapshot();
 7323    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7324
 7325    cx.update(|_, cx| {
 7326        provider.update(cx, |provider, _| {
 7327            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7328                id: None,
 7329                edits: vec![(edit_position..edit_position, "X".into())],
 7330                edit_preview: None,
 7331            }))
 7332        })
 7333    });
 7334
 7335    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7336    cx.update_editor(|editor, window, cx| {
 7337        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7338    });
 7339
 7340    cx.assert_editor_state(indoc! {"
 7341        line 1
 7342        line 2
 7343        lineXˇ 3
 7344        line 4
 7345        line 5
 7346        line 6
 7347        line 7
 7348        line 8
 7349        line 9
 7350        line 10
 7351    "});
 7352
 7353    cx.update_editor(|editor, window, cx| {
 7354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7355            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7356        });
 7357    });
 7358
 7359    cx.assert_editor_state(indoc! {"
 7360        line 1
 7361        line 2
 7362        lineX 3
 7363        line 4
 7364        line 5
 7365        line 6
 7366        line 7
 7367        line 8
 7368        line 9
 7369        liˇne 10
 7370    "});
 7371
 7372    cx.update_editor(|editor, window, cx| {
 7373        editor.undo(&Default::default(), window, cx);
 7374    });
 7375
 7376    cx.assert_editor_state(indoc! {"
 7377        line 1
 7378        line 2
 7379        lineˇ 3
 7380        line 4
 7381        line 5
 7382        line 6
 7383        line 7
 7384        line 8
 7385        line 9
 7386        line 10
 7387    "});
 7388}
 7389
 7390#[gpui::test]
 7391async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7392    init_test(cx, |_| {});
 7393
 7394    let mut cx = EditorTestContext::new(cx).await;
 7395    cx.set_state(
 7396        r#"let foo = 2;
 7397lˇet foo = 2;
 7398let fooˇ = 2;
 7399let foo = 2;
 7400let foo = ˇ2;"#,
 7401    );
 7402
 7403    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7404        .unwrap();
 7405    cx.assert_editor_state(
 7406        r#"let foo = 2;
 7407«letˇ» foo = 2;
 7408let «fooˇ» = 2;
 7409let foo = 2;
 7410let foo = «2ˇ»;"#,
 7411    );
 7412
 7413    // noop for multiple selections with different contents
 7414    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7415        .unwrap();
 7416    cx.assert_editor_state(
 7417        r#"let foo = 2;
 7418«letˇ» foo = 2;
 7419let «fooˇ» = 2;
 7420let foo = 2;
 7421let foo = «2ˇ»;"#,
 7422    );
 7423
 7424    // Test last selection direction should be preserved
 7425    cx.set_state(
 7426        r#"let foo = 2;
 7427let foo = 2;
 7428let «fooˇ» = 2;
 7429let «ˇfoo» = 2;
 7430let foo = 2;"#,
 7431    );
 7432
 7433    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7434        .unwrap();
 7435    cx.assert_editor_state(
 7436        r#"let foo = 2;
 7437let foo = 2;
 7438let «fooˇ» = 2;
 7439let «ˇfoo» = 2;
 7440let «ˇfoo» = 2;"#,
 7441    );
 7442}
 7443
 7444#[gpui::test]
 7445async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7446    init_test(cx, |_| {});
 7447
 7448    let mut cx =
 7449        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7450
 7451    cx.assert_editor_state(indoc! {"
 7452        ˇbbb
 7453        ccc
 7454
 7455        bbb
 7456        ccc
 7457        "});
 7458    cx.dispatch_action(SelectPrevious::default());
 7459    cx.assert_editor_state(indoc! {"
 7460                «bbbˇ»
 7461                ccc
 7462
 7463                bbb
 7464                ccc
 7465                "});
 7466    cx.dispatch_action(SelectPrevious::default());
 7467    cx.assert_editor_state(indoc! {"
 7468                «bbbˇ»
 7469                ccc
 7470
 7471                «bbbˇ»
 7472                ccc
 7473                "});
 7474}
 7475
 7476#[gpui::test]
 7477async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7478    init_test(cx, |_| {});
 7479
 7480    let mut cx = EditorTestContext::new(cx).await;
 7481    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7482
 7483    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7484        .unwrap();
 7485    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7486
 7487    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7488        .unwrap();
 7489    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7490
 7491    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7492    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7493
 7494    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7495    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7496
 7497    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7498        .unwrap();
 7499    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7500
 7501    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7502        .unwrap();
 7503    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7504}
 7505
 7506#[gpui::test]
 7507async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7508    init_test(cx, |_| {});
 7509
 7510    let mut cx = EditorTestContext::new(cx).await;
 7511    cx.set_state("");
 7512
 7513    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7514        .unwrap();
 7515    cx.assert_editor_state("«aˇ»");
 7516    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7517        .unwrap();
 7518    cx.assert_editor_state("«aˇ»");
 7519}
 7520
 7521#[gpui::test]
 7522async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7523    init_test(cx, |_| {});
 7524
 7525    let mut cx = EditorTestContext::new(cx).await;
 7526    cx.set_state(
 7527        r#"let foo = 2;
 7528lˇet foo = 2;
 7529let fooˇ = 2;
 7530let foo = 2;
 7531let foo = ˇ2;"#,
 7532    );
 7533
 7534    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7535        .unwrap();
 7536    cx.assert_editor_state(
 7537        r#"let foo = 2;
 7538«letˇ» foo = 2;
 7539let «fooˇ» = 2;
 7540let foo = 2;
 7541let foo = «2ˇ»;"#,
 7542    );
 7543
 7544    // noop for multiple selections with different contents
 7545    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7546        .unwrap();
 7547    cx.assert_editor_state(
 7548        r#"let foo = 2;
 7549«letˇ» foo = 2;
 7550let «fooˇ» = 2;
 7551let foo = 2;
 7552let foo = «2ˇ»;"#,
 7553    );
 7554}
 7555
 7556#[gpui::test]
 7557async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7558    init_test(cx, |_| {});
 7559
 7560    let mut cx = EditorTestContext::new(cx).await;
 7561    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7562
 7563    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7564        .unwrap();
 7565    // selection direction is preserved
 7566    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7567
 7568    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7569        .unwrap();
 7570    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7571
 7572    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7573    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7574
 7575    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7576    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7577
 7578    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7579        .unwrap();
 7580    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7581
 7582    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7583        .unwrap();
 7584    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7585}
 7586
 7587#[gpui::test]
 7588async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7589    init_test(cx, |_| {});
 7590
 7591    let language = Arc::new(Language::new(
 7592        LanguageConfig::default(),
 7593        Some(tree_sitter_rust::LANGUAGE.into()),
 7594    ));
 7595
 7596    let text = r#"
 7597        use mod1::mod2::{mod3, mod4};
 7598
 7599        fn fn_1(param1: bool, param2: &str) {
 7600            let var1 = "text";
 7601        }
 7602    "#
 7603    .unindent();
 7604
 7605    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7606    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7607    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7608
 7609    editor
 7610        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7611        .await;
 7612
 7613    editor.update_in(cx, |editor, window, cx| {
 7614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7615            s.select_display_ranges([
 7616                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7617                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7618                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7619            ]);
 7620        });
 7621        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7622    });
 7623    editor.update(cx, |editor, cx| {
 7624        assert_text_with_selections(
 7625            editor,
 7626            indoc! {r#"
 7627                use mod1::mod2::{mod3, «mod4ˇ»};
 7628
 7629                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7630                    let var1 = "«ˇtext»";
 7631                }
 7632            "#},
 7633            cx,
 7634        );
 7635    });
 7636
 7637    editor.update_in(cx, |editor, window, cx| {
 7638        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7639    });
 7640    editor.update(cx, |editor, cx| {
 7641        assert_text_with_selections(
 7642            editor,
 7643            indoc! {r#"
 7644                use mod1::mod2::«{mod3, mod4}ˇ»;
 7645
 7646                «ˇfn fn_1(param1: bool, param2: &str) {
 7647                    let var1 = "text";
 7648 7649            "#},
 7650            cx,
 7651        );
 7652    });
 7653
 7654    editor.update_in(cx, |editor, window, cx| {
 7655        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7656    });
 7657    assert_eq!(
 7658        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7659        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7660    );
 7661
 7662    // Trying to expand the selected syntax node one more time has no effect.
 7663    editor.update_in(cx, |editor, window, cx| {
 7664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7665    });
 7666    assert_eq!(
 7667        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7668        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7669    );
 7670
 7671    editor.update_in(cx, |editor, window, cx| {
 7672        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7673    });
 7674    editor.update(cx, |editor, cx| {
 7675        assert_text_with_selections(
 7676            editor,
 7677            indoc! {r#"
 7678                use mod1::mod2::«{mod3, mod4}ˇ»;
 7679
 7680                «ˇfn fn_1(param1: bool, param2: &str) {
 7681                    let var1 = "text";
 7682 7683            "#},
 7684            cx,
 7685        );
 7686    });
 7687
 7688    editor.update_in(cx, |editor, window, cx| {
 7689        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7690    });
 7691    editor.update(cx, |editor, cx| {
 7692        assert_text_with_selections(
 7693            editor,
 7694            indoc! {r#"
 7695                use mod1::mod2::{mod3, «mod4ˇ»};
 7696
 7697                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7698                    let var1 = "«ˇtext»";
 7699                }
 7700            "#},
 7701            cx,
 7702        );
 7703    });
 7704
 7705    editor.update_in(cx, |editor, window, cx| {
 7706        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7707    });
 7708    editor.update(cx, |editor, cx| {
 7709        assert_text_with_selections(
 7710            editor,
 7711            indoc! {r#"
 7712                use mod1::mod2::{mod3, mo«ˇ»d4};
 7713
 7714                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7715                    let var1 = "te«ˇ»xt";
 7716                }
 7717            "#},
 7718            cx,
 7719        );
 7720    });
 7721
 7722    // Trying to shrink the selected syntax node one more time has no effect.
 7723    editor.update_in(cx, |editor, window, cx| {
 7724        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7725    });
 7726    editor.update_in(cx, |editor, _, cx| {
 7727        assert_text_with_selections(
 7728            editor,
 7729            indoc! {r#"
 7730                use mod1::mod2::{mod3, mo«ˇ»d4};
 7731
 7732                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7733                    let var1 = "te«ˇ»xt";
 7734                }
 7735            "#},
 7736            cx,
 7737        );
 7738    });
 7739
 7740    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7741    // a fold.
 7742    editor.update_in(cx, |editor, window, cx| {
 7743        editor.fold_creases(
 7744            vec![
 7745                Crease::simple(
 7746                    Point::new(0, 21)..Point::new(0, 24),
 7747                    FoldPlaceholder::test(),
 7748                ),
 7749                Crease::simple(
 7750                    Point::new(3, 20)..Point::new(3, 22),
 7751                    FoldPlaceholder::test(),
 7752                ),
 7753            ],
 7754            true,
 7755            window,
 7756            cx,
 7757        );
 7758        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7759    });
 7760    editor.update(cx, |editor, cx| {
 7761        assert_text_with_selections(
 7762            editor,
 7763            indoc! {r#"
 7764                use mod1::mod2::«{mod3, mod4}ˇ»;
 7765
 7766                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7767                    let var1 = "«ˇtext»";
 7768                }
 7769            "#},
 7770            cx,
 7771        );
 7772    });
 7773}
 7774
 7775#[gpui::test]
 7776async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7777    init_test(cx, |_| {});
 7778
 7779    let language = Arc::new(Language::new(
 7780        LanguageConfig::default(),
 7781        Some(tree_sitter_rust::LANGUAGE.into()),
 7782    ));
 7783
 7784    let text = "let a = 2;";
 7785
 7786    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7788    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7789
 7790    editor
 7791        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7792        .await;
 7793
 7794    // Test case 1: Cursor at end of word
 7795    editor.update_in(cx, |editor, window, cx| {
 7796        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7797            s.select_display_ranges([
 7798                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7799            ]);
 7800        });
 7801    });
 7802    editor.update(cx, |editor, cx| {
 7803        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7804    });
 7805    editor.update_in(cx, |editor, window, cx| {
 7806        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7807    });
 7808    editor.update(cx, |editor, cx| {
 7809        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7810    });
 7811    editor.update_in(cx, |editor, window, cx| {
 7812        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7813    });
 7814    editor.update(cx, |editor, cx| {
 7815        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7816    });
 7817
 7818    // Test case 2: Cursor at end of statement
 7819    editor.update_in(cx, |editor, window, cx| {
 7820        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7821            s.select_display_ranges([
 7822                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7823            ]);
 7824        });
 7825    });
 7826    editor.update(cx, |editor, cx| {
 7827        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7828    });
 7829    editor.update_in(cx, |editor, window, cx| {
 7830        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7831    });
 7832    editor.update(cx, |editor, cx| {
 7833        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7834    });
 7835}
 7836
 7837#[gpui::test]
 7838async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7839    init_test(cx, |_| {});
 7840
 7841    let language = Arc::new(Language::new(
 7842        LanguageConfig::default(),
 7843        Some(tree_sitter_rust::LANGUAGE.into()),
 7844    ));
 7845
 7846    let text = r#"
 7847        use mod1::mod2::{mod3, mod4};
 7848
 7849        fn fn_1(param1: bool, param2: &str) {
 7850            let var1 = "hello world";
 7851        }
 7852    "#
 7853    .unindent();
 7854
 7855    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7856    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7857    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7858
 7859    editor
 7860        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7861        .await;
 7862
 7863    // Test 1: Cursor on a letter of a string word
 7864    editor.update_in(cx, |editor, window, cx| {
 7865        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7866            s.select_display_ranges([
 7867                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7868            ]);
 7869        });
 7870    });
 7871    editor.update_in(cx, |editor, window, cx| {
 7872        assert_text_with_selections(
 7873            editor,
 7874            indoc! {r#"
 7875                use mod1::mod2::{mod3, mod4};
 7876
 7877                fn fn_1(param1: bool, param2: &str) {
 7878                    let var1 = "hˇello world";
 7879                }
 7880            "#},
 7881            cx,
 7882        );
 7883        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7884        assert_text_with_selections(
 7885            editor,
 7886            indoc! {r#"
 7887                use mod1::mod2::{mod3, mod4};
 7888
 7889                fn fn_1(param1: bool, param2: &str) {
 7890                    let var1 = "«ˇhello» world";
 7891                }
 7892            "#},
 7893            cx,
 7894        );
 7895    });
 7896
 7897    // Test 2: Partial selection within a word
 7898    editor.update_in(cx, |editor, window, cx| {
 7899        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7900            s.select_display_ranges([
 7901                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 7902            ]);
 7903        });
 7904    });
 7905    editor.update_in(cx, |editor, window, cx| {
 7906        assert_text_with_selections(
 7907            editor,
 7908            indoc! {r#"
 7909                use mod1::mod2::{mod3, mod4};
 7910
 7911                fn fn_1(param1: bool, param2: &str) {
 7912                    let var1 = "h«elˇ»lo world";
 7913                }
 7914            "#},
 7915            cx,
 7916        );
 7917        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7918        assert_text_with_selections(
 7919            editor,
 7920            indoc! {r#"
 7921                use mod1::mod2::{mod3, mod4};
 7922
 7923                fn fn_1(param1: bool, param2: &str) {
 7924                    let var1 = "«ˇhello» world";
 7925                }
 7926            "#},
 7927            cx,
 7928        );
 7929    });
 7930
 7931    // Test 3: Complete word already selected
 7932    editor.update_in(cx, |editor, window, cx| {
 7933        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7934            s.select_display_ranges([
 7935                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 7936            ]);
 7937        });
 7938    });
 7939    editor.update_in(cx, |editor, window, cx| {
 7940        assert_text_with_selections(
 7941            editor,
 7942            indoc! {r#"
 7943                use mod1::mod2::{mod3, mod4};
 7944
 7945                fn fn_1(param1: bool, param2: &str) {
 7946                    let var1 = "«helloˇ» world";
 7947                }
 7948            "#},
 7949            cx,
 7950        );
 7951        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7952        assert_text_with_selections(
 7953            editor,
 7954            indoc! {r#"
 7955                use mod1::mod2::{mod3, mod4};
 7956
 7957                fn fn_1(param1: bool, param2: &str) {
 7958                    let var1 = "«hello worldˇ»";
 7959                }
 7960            "#},
 7961            cx,
 7962        );
 7963    });
 7964
 7965    // Test 4: Selection spanning across words
 7966    editor.update_in(cx, |editor, window, cx| {
 7967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7968            s.select_display_ranges([
 7969                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 7970            ]);
 7971        });
 7972    });
 7973    editor.update_in(cx, |editor, window, cx| {
 7974        assert_text_with_selections(
 7975            editor,
 7976            indoc! {r#"
 7977                use mod1::mod2::{mod3, mod4};
 7978
 7979                fn fn_1(param1: bool, param2: &str) {
 7980                    let var1 = "hel«lo woˇ»rld";
 7981                }
 7982            "#},
 7983            cx,
 7984        );
 7985        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7986        assert_text_with_selections(
 7987            editor,
 7988            indoc! {r#"
 7989                use mod1::mod2::{mod3, mod4};
 7990
 7991                fn fn_1(param1: bool, param2: &str) {
 7992                    let var1 = "«ˇhello world»";
 7993                }
 7994            "#},
 7995            cx,
 7996        );
 7997    });
 7998
 7999    // Test 5: Expansion beyond string
 8000    editor.update_in(cx, |editor, window, cx| {
 8001        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8002        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8003        assert_text_with_selections(
 8004            editor,
 8005            indoc! {r#"
 8006                use mod1::mod2::{mod3, mod4};
 8007
 8008                fn fn_1(param1: bool, param2: &str) {
 8009                    «ˇlet var1 = "hello world";»
 8010                }
 8011            "#},
 8012            cx,
 8013        );
 8014    });
 8015}
 8016
 8017#[gpui::test]
 8018async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8019    init_test(cx, |_| {});
 8020
 8021    let mut cx = EditorTestContext::new(cx).await;
 8022
 8023    let language = Arc::new(Language::new(
 8024        LanguageConfig::default(),
 8025        Some(tree_sitter_rust::LANGUAGE.into()),
 8026    ));
 8027
 8028    cx.update_buffer(|buffer, cx| {
 8029        buffer.set_language(Some(language), cx);
 8030    });
 8031
 8032    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8033    cx.update_editor(|editor, window, cx| {
 8034        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8035    });
 8036
 8037    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8038}
 8039
 8040#[gpui::test]
 8041async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8042    init_test(cx, |_| {});
 8043
 8044    let base_text = r#"
 8045        impl A {
 8046            // this is an uncommitted comment
 8047
 8048            fn b() {
 8049                c();
 8050            }
 8051
 8052            // this is another uncommitted comment
 8053
 8054            fn d() {
 8055                // e
 8056                // f
 8057            }
 8058        }
 8059
 8060        fn g() {
 8061            // h
 8062        }
 8063    "#
 8064    .unindent();
 8065
 8066    let text = r#"
 8067        ˇimpl A {
 8068
 8069            fn b() {
 8070                c();
 8071            }
 8072
 8073            fn d() {
 8074                // e
 8075                // f
 8076            }
 8077        }
 8078
 8079        fn g() {
 8080            // h
 8081        }
 8082    "#
 8083    .unindent();
 8084
 8085    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8086    cx.set_state(&text);
 8087    cx.set_head_text(&base_text);
 8088    cx.update_editor(|editor, window, cx| {
 8089        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8090    });
 8091
 8092    cx.assert_state_with_diff(
 8093        "
 8094        ˇimpl A {
 8095      -     // this is an uncommitted comment
 8096
 8097            fn b() {
 8098                c();
 8099            }
 8100
 8101      -     // this is another uncommitted comment
 8102      -
 8103            fn d() {
 8104                // e
 8105                // f
 8106            }
 8107        }
 8108
 8109        fn g() {
 8110            // h
 8111        }
 8112    "
 8113        .unindent(),
 8114    );
 8115
 8116    let expected_display_text = "
 8117        impl A {
 8118            // this is an uncommitted comment
 8119
 8120            fn b() {
 8121 8122            }
 8123
 8124            // this is another uncommitted comment
 8125
 8126            fn d() {
 8127 8128            }
 8129        }
 8130
 8131        fn g() {
 8132 8133        }
 8134        "
 8135    .unindent();
 8136
 8137    cx.update_editor(|editor, window, cx| {
 8138        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8139        assert_eq!(editor.display_text(cx), expected_display_text);
 8140    });
 8141}
 8142
 8143#[gpui::test]
 8144async fn test_autoindent(cx: &mut TestAppContext) {
 8145    init_test(cx, |_| {});
 8146
 8147    let language = Arc::new(
 8148        Language::new(
 8149            LanguageConfig {
 8150                brackets: BracketPairConfig {
 8151                    pairs: vec![
 8152                        BracketPair {
 8153                            start: "{".to_string(),
 8154                            end: "}".to_string(),
 8155                            close: false,
 8156                            surround: false,
 8157                            newline: true,
 8158                        },
 8159                        BracketPair {
 8160                            start: "(".to_string(),
 8161                            end: ")".to_string(),
 8162                            close: false,
 8163                            surround: false,
 8164                            newline: true,
 8165                        },
 8166                    ],
 8167                    ..Default::default()
 8168                },
 8169                ..Default::default()
 8170            },
 8171            Some(tree_sitter_rust::LANGUAGE.into()),
 8172        )
 8173        .with_indents_query(
 8174            r#"
 8175                (_ "(" ")" @end) @indent
 8176                (_ "{" "}" @end) @indent
 8177            "#,
 8178        )
 8179        .unwrap(),
 8180    );
 8181
 8182    let text = "fn a() {}";
 8183
 8184    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8185    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8186    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8187    editor
 8188        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8189        .await;
 8190
 8191    editor.update_in(cx, |editor, window, cx| {
 8192        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8193            s.select_ranges([5..5, 8..8, 9..9])
 8194        });
 8195        editor.newline(&Newline, window, cx);
 8196        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8197        assert_eq!(
 8198            editor.selections.ranges(cx),
 8199            &[
 8200                Point::new(1, 4)..Point::new(1, 4),
 8201                Point::new(3, 4)..Point::new(3, 4),
 8202                Point::new(5, 0)..Point::new(5, 0)
 8203            ]
 8204        );
 8205    });
 8206}
 8207
 8208#[gpui::test]
 8209async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 8210    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 8211
 8212    let language = Arc::new(
 8213        Language::new(
 8214            LanguageConfig {
 8215                brackets: BracketPairConfig {
 8216                    pairs: vec![
 8217                        BracketPair {
 8218                            start: "{".to_string(),
 8219                            end: "}".to_string(),
 8220                            close: false,
 8221                            surround: false,
 8222                            newline: true,
 8223                        },
 8224                        BracketPair {
 8225                            start: "(".to_string(),
 8226                            end: ")".to_string(),
 8227                            close: false,
 8228                            surround: false,
 8229                            newline: true,
 8230                        },
 8231                    ],
 8232                    ..Default::default()
 8233                },
 8234                ..Default::default()
 8235            },
 8236            Some(tree_sitter_rust::LANGUAGE.into()),
 8237        )
 8238        .with_indents_query(
 8239            r#"
 8240                (_ "(" ")" @end) @indent
 8241                (_ "{" "}" @end) @indent
 8242            "#,
 8243        )
 8244        .unwrap(),
 8245    );
 8246
 8247    let text = "fn a() {}";
 8248
 8249    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8251    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8252    editor
 8253        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8254        .await;
 8255
 8256    editor.update_in(cx, |editor, window, cx| {
 8257        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8258            s.select_ranges([5..5, 8..8, 9..9])
 8259        });
 8260        editor.newline(&Newline, window, cx);
 8261        assert_eq!(
 8262            editor.text(cx),
 8263            indoc!(
 8264                "
 8265                fn a(
 8266
 8267                ) {
 8268
 8269                }
 8270                "
 8271            )
 8272        );
 8273        assert_eq!(
 8274            editor.selections.ranges(cx),
 8275            &[
 8276                Point::new(1, 0)..Point::new(1, 0),
 8277                Point::new(3, 0)..Point::new(3, 0),
 8278                Point::new(5, 0)..Point::new(5, 0)
 8279            ]
 8280        );
 8281    });
 8282}
 8283
 8284#[gpui::test]
 8285async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 8286    init_test(cx, |settings| {
 8287        settings.defaults.auto_indent = Some(true);
 8288        settings.languages.0.insert(
 8289            "python".into(),
 8290            LanguageSettingsContent {
 8291                auto_indent: Some(false),
 8292                ..Default::default()
 8293            },
 8294        );
 8295    });
 8296
 8297    let mut cx = EditorTestContext::new(cx).await;
 8298
 8299    let injected_language = Arc::new(
 8300        Language::new(
 8301            LanguageConfig {
 8302                brackets: BracketPairConfig {
 8303                    pairs: vec![
 8304                        BracketPair {
 8305                            start: "{".to_string(),
 8306                            end: "}".to_string(),
 8307                            close: false,
 8308                            surround: false,
 8309                            newline: true,
 8310                        },
 8311                        BracketPair {
 8312                            start: "(".to_string(),
 8313                            end: ")".to_string(),
 8314                            close: true,
 8315                            surround: false,
 8316                            newline: true,
 8317                        },
 8318                    ],
 8319                    ..Default::default()
 8320                },
 8321                name: "python".into(),
 8322                ..Default::default()
 8323            },
 8324            Some(tree_sitter_python::LANGUAGE.into()),
 8325        )
 8326        .with_indents_query(
 8327            r#"
 8328                (_ "(" ")" @end) @indent
 8329                (_ "{" "}" @end) @indent
 8330            "#,
 8331        )
 8332        .unwrap(),
 8333    );
 8334
 8335    let language = Arc::new(
 8336        Language::new(
 8337            LanguageConfig {
 8338                brackets: BracketPairConfig {
 8339                    pairs: vec![
 8340                        BracketPair {
 8341                            start: "{".to_string(),
 8342                            end: "}".to_string(),
 8343                            close: false,
 8344                            surround: false,
 8345                            newline: true,
 8346                        },
 8347                        BracketPair {
 8348                            start: "(".to_string(),
 8349                            end: ")".to_string(),
 8350                            close: true,
 8351                            surround: false,
 8352                            newline: true,
 8353                        },
 8354                    ],
 8355                    ..Default::default()
 8356                },
 8357                name: LanguageName::new("rust"),
 8358                ..Default::default()
 8359            },
 8360            Some(tree_sitter_rust::LANGUAGE.into()),
 8361        )
 8362        .with_indents_query(
 8363            r#"
 8364                (_ "(" ")" @end) @indent
 8365                (_ "{" "}" @end) @indent
 8366            "#,
 8367        )
 8368        .unwrap()
 8369        .with_injection_query(
 8370            r#"
 8371            (macro_invocation
 8372                macro: (identifier) @_macro_name
 8373                (token_tree) @injection.content
 8374                (#set! injection.language "python"))
 8375           "#,
 8376        )
 8377        .unwrap(),
 8378    );
 8379
 8380    cx.language_registry().add(injected_language);
 8381    cx.language_registry().add(language.clone());
 8382
 8383    cx.update_buffer(|buffer, cx| {
 8384        buffer.set_language(Some(language), cx);
 8385    });
 8386
 8387    cx.set_state(r#"struct A {ˇ}"#);
 8388
 8389    cx.update_editor(|editor, window, cx| {
 8390        editor.newline(&Default::default(), window, cx);
 8391    });
 8392
 8393    cx.assert_editor_state(indoc!(
 8394        "struct A {
 8395            ˇ
 8396        }"
 8397    ));
 8398
 8399    cx.set_state(r#"select_biased!(ˇ)"#);
 8400
 8401    cx.update_editor(|editor, window, cx| {
 8402        editor.newline(&Default::default(), window, cx);
 8403        editor.handle_input("def ", window, cx);
 8404        editor.handle_input("(", window, cx);
 8405        editor.newline(&Default::default(), window, cx);
 8406        editor.handle_input("a", window, cx);
 8407    });
 8408
 8409    cx.assert_editor_state(indoc!(
 8410        "select_biased!(
 8411        def (
 8412 8413        )
 8414        )"
 8415    ));
 8416}
 8417
 8418#[gpui::test]
 8419async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8420    init_test(cx, |_| {});
 8421
 8422    {
 8423        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8424        cx.set_state(indoc! {"
 8425            impl A {
 8426
 8427                fn b() {}
 8428
 8429            «fn c() {
 8430
 8431            }ˇ»
 8432            }
 8433        "});
 8434
 8435        cx.update_editor(|editor, window, cx| {
 8436            editor.autoindent(&Default::default(), window, cx);
 8437        });
 8438
 8439        cx.assert_editor_state(indoc! {"
 8440            impl A {
 8441
 8442                fn b() {}
 8443
 8444                «fn c() {
 8445
 8446                }ˇ»
 8447            }
 8448        "});
 8449    }
 8450
 8451    {
 8452        let mut cx = EditorTestContext::new_multibuffer(
 8453            cx,
 8454            [indoc! { "
 8455                impl A {
 8456                «
 8457                // a
 8458                fn b(){}
 8459                »
 8460                «
 8461                    }
 8462                    fn c(){}
 8463                »
 8464            "}],
 8465        );
 8466
 8467        let buffer = cx.update_editor(|editor, _, cx| {
 8468            let buffer = editor.buffer().update(cx, |buffer, _| {
 8469                buffer.all_buffers().iter().next().unwrap().clone()
 8470            });
 8471            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8472            buffer
 8473        });
 8474
 8475        cx.run_until_parked();
 8476        cx.update_editor(|editor, window, cx| {
 8477            editor.select_all(&Default::default(), window, cx);
 8478            editor.autoindent(&Default::default(), window, cx)
 8479        });
 8480        cx.run_until_parked();
 8481
 8482        cx.update(|_, cx| {
 8483            assert_eq!(
 8484                buffer.read(cx).text(),
 8485                indoc! { "
 8486                    impl A {
 8487
 8488                        // a
 8489                        fn b(){}
 8490
 8491
 8492                    }
 8493                    fn c(){}
 8494
 8495                " }
 8496            )
 8497        });
 8498    }
 8499}
 8500
 8501#[gpui::test]
 8502async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8503    init_test(cx, |_| {});
 8504
 8505    let mut cx = EditorTestContext::new(cx).await;
 8506
 8507    let language = Arc::new(Language::new(
 8508        LanguageConfig {
 8509            brackets: BracketPairConfig {
 8510                pairs: vec![
 8511                    BracketPair {
 8512                        start: "{".to_string(),
 8513                        end: "}".to_string(),
 8514                        close: true,
 8515                        surround: true,
 8516                        newline: true,
 8517                    },
 8518                    BracketPair {
 8519                        start: "(".to_string(),
 8520                        end: ")".to_string(),
 8521                        close: true,
 8522                        surround: true,
 8523                        newline: true,
 8524                    },
 8525                    BracketPair {
 8526                        start: "/*".to_string(),
 8527                        end: " */".to_string(),
 8528                        close: true,
 8529                        surround: true,
 8530                        newline: true,
 8531                    },
 8532                    BracketPair {
 8533                        start: "[".to_string(),
 8534                        end: "]".to_string(),
 8535                        close: false,
 8536                        surround: false,
 8537                        newline: true,
 8538                    },
 8539                    BracketPair {
 8540                        start: "\"".to_string(),
 8541                        end: "\"".to_string(),
 8542                        close: true,
 8543                        surround: true,
 8544                        newline: false,
 8545                    },
 8546                    BracketPair {
 8547                        start: "<".to_string(),
 8548                        end: ">".to_string(),
 8549                        close: false,
 8550                        surround: true,
 8551                        newline: true,
 8552                    },
 8553                ],
 8554                ..Default::default()
 8555            },
 8556            autoclose_before: "})]".to_string(),
 8557            ..Default::default()
 8558        },
 8559        Some(tree_sitter_rust::LANGUAGE.into()),
 8560    ));
 8561
 8562    cx.language_registry().add(language.clone());
 8563    cx.update_buffer(|buffer, cx| {
 8564        buffer.set_language(Some(language), cx);
 8565    });
 8566
 8567    cx.set_state(
 8568        &r#"
 8569            🏀ˇ
 8570            εˇ
 8571            ❤️ˇ
 8572        "#
 8573        .unindent(),
 8574    );
 8575
 8576    // autoclose multiple nested brackets at multiple cursors
 8577    cx.update_editor(|editor, window, cx| {
 8578        editor.handle_input("{", window, cx);
 8579        editor.handle_input("{", window, cx);
 8580        editor.handle_input("{", window, cx);
 8581    });
 8582    cx.assert_editor_state(
 8583        &"
 8584            🏀{{{ˇ}}}
 8585            ε{{{ˇ}}}
 8586            ❤️{{{ˇ}}}
 8587        "
 8588        .unindent(),
 8589    );
 8590
 8591    // insert a different closing bracket
 8592    cx.update_editor(|editor, window, cx| {
 8593        editor.handle_input(")", window, cx);
 8594    });
 8595    cx.assert_editor_state(
 8596        &"
 8597            🏀{{{)ˇ}}}
 8598            ε{{{)ˇ}}}
 8599            ❤️{{{)ˇ}}}
 8600        "
 8601        .unindent(),
 8602    );
 8603
 8604    // skip over the auto-closed brackets when typing a closing bracket
 8605    cx.update_editor(|editor, window, cx| {
 8606        editor.move_right(&MoveRight, window, cx);
 8607        editor.handle_input("}", window, cx);
 8608        editor.handle_input("}", window, cx);
 8609        editor.handle_input("}", window, cx);
 8610    });
 8611    cx.assert_editor_state(
 8612        &"
 8613            🏀{{{)}}}}ˇ
 8614            ε{{{)}}}}ˇ
 8615            ❤️{{{)}}}}ˇ
 8616        "
 8617        .unindent(),
 8618    );
 8619
 8620    // autoclose multi-character pairs
 8621    cx.set_state(
 8622        &"
 8623            ˇ
 8624            ˇ
 8625        "
 8626        .unindent(),
 8627    );
 8628    cx.update_editor(|editor, window, cx| {
 8629        editor.handle_input("/", window, cx);
 8630        editor.handle_input("*", window, cx);
 8631    });
 8632    cx.assert_editor_state(
 8633        &"
 8634            /*ˇ */
 8635            /*ˇ */
 8636        "
 8637        .unindent(),
 8638    );
 8639
 8640    // one cursor autocloses a multi-character pair, one cursor
 8641    // does not autoclose.
 8642    cx.set_state(
 8643        &"
 8644 8645            ˇ
 8646        "
 8647        .unindent(),
 8648    );
 8649    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8650    cx.assert_editor_state(
 8651        &"
 8652            /*ˇ */
 8653 8654        "
 8655        .unindent(),
 8656    );
 8657
 8658    // Don't autoclose if the next character isn't whitespace and isn't
 8659    // listed in the language's "autoclose_before" section.
 8660    cx.set_state("ˇa b");
 8661    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8662    cx.assert_editor_state("{ˇa b");
 8663
 8664    // Don't autoclose if `close` is false for the bracket pair
 8665    cx.set_state("ˇ");
 8666    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8667    cx.assert_editor_state("");
 8668
 8669    // Surround with brackets if text is selected
 8670    cx.set_state("«aˇ» b");
 8671    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8672    cx.assert_editor_state("{«aˇ»} b");
 8673
 8674    // Autoclose when not immediately after a word character
 8675    cx.set_state("a ˇ");
 8676    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8677    cx.assert_editor_state("a \"ˇ\"");
 8678
 8679    // Autoclose pair where the start and end characters are the same
 8680    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8681    cx.assert_editor_state("a \"\"ˇ");
 8682
 8683    // Don't autoclose when immediately after a word character
 8684    cx.set_state("");
 8685    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8686    cx.assert_editor_state("a\"ˇ");
 8687
 8688    // Do autoclose when after a non-word character
 8689    cx.set_state("");
 8690    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8691    cx.assert_editor_state("{\"ˇ\"");
 8692
 8693    // Non identical pairs autoclose regardless of preceding character
 8694    cx.set_state("");
 8695    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8696    cx.assert_editor_state("a{ˇ}");
 8697
 8698    // Don't autoclose pair if autoclose is disabled
 8699    cx.set_state("ˇ");
 8700    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8701    cx.assert_editor_state("");
 8702
 8703    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8704    cx.set_state("«aˇ» b");
 8705    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8706    cx.assert_editor_state("<«aˇ»> b");
 8707}
 8708
 8709#[gpui::test]
 8710async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8711    init_test(cx, |settings| {
 8712        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8713    });
 8714
 8715    let mut cx = EditorTestContext::new(cx).await;
 8716
 8717    let language = Arc::new(Language::new(
 8718        LanguageConfig {
 8719            brackets: BracketPairConfig {
 8720                pairs: vec![
 8721                    BracketPair {
 8722                        start: "{".to_string(),
 8723                        end: "}".to_string(),
 8724                        close: true,
 8725                        surround: true,
 8726                        newline: true,
 8727                    },
 8728                    BracketPair {
 8729                        start: "(".to_string(),
 8730                        end: ")".to_string(),
 8731                        close: true,
 8732                        surround: true,
 8733                        newline: true,
 8734                    },
 8735                    BracketPair {
 8736                        start: "[".to_string(),
 8737                        end: "]".to_string(),
 8738                        close: false,
 8739                        surround: false,
 8740                        newline: true,
 8741                    },
 8742                ],
 8743                ..Default::default()
 8744            },
 8745            autoclose_before: "})]".to_string(),
 8746            ..Default::default()
 8747        },
 8748        Some(tree_sitter_rust::LANGUAGE.into()),
 8749    ));
 8750
 8751    cx.language_registry().add(language.clone());
 8752    cx.update_buffer(|buffer, cx| {
 8753        buffer.set_language(Some(language), cx);
 8754    });
 8755
 8756    cx.set_state(
 8757        &"
 8758            ˇ
 8759            ˇ
 8760            ˇ
 8761        "
 8762        .unindent(),
 8763    );
 8764
 8765    // ensure only matching closing brackets are skipped over
 8766    cx.update_editor(|editor, window, cx| {
 8767        editor.handle_input("}", window, cx);
 8768        editor.move_left(&MoveLeft, window, cx);
 8769        editor.handle_input(")", window, cx);
 8770        editor.move_left(&MoveLeft, window, cx);
 8771    });
 8772    cx.assert_editor_state(
 8773        &"
 8774            ˇ)}
 8775            ˇ)}
 8776            ˇ)}
 8777        "
 8778        .unindent(),
 8779    );
 8780
 8781    // skip-over closing brackets at multiple cursors
 8782    cx.update_editor(|editor, window, cx| {
 8783        editor.handle_input(")", window, cx);
 8784        editor.handle_input("}", window, cx);
 8785    });
 8786    cx.assert_editor_state(
 8787        &"
 8788            )}ˇ
 8789            )}ˇ
 8790            )}ˇ
 8791        "
 8792        .unindent(),
 8793    );
 8794
 8795    // ignore non-close brackets
 8796    cx.update_editor(|editor, window, cx| {
 8797        editor.handle_input("]", window, cx);
 8798        editor.move_left(&MoveLeft, window, cx);
 8799        editor.handle_input("]", window, cx);
 8800    });
 8801    cx.assert_editor_state(
 8802        &"
 8803            )}]ˇ]
 8804            )}]ˇ]
 8805            )}]ˇ]
 8806        "
 8807        .unindent(),
 8808    );
 8809}
 8810
 8811#[gpui::test]
 8812async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8813    init_test(cx, |_| {});
 8814
 8815    let mut cx = EditorTestContext::new(cx).await;
 8816
 8817    let html_language = Arc::new(
 8818        Language::new(
 8819            LanguageConfig {
 8820                name: "HTML".into(),
 8821                brackets: BracketPairConfig {
 8822                    pairs: vec![
 8823                        BracketPair {
 8824                            start: "<".into(),
 8825                            end: ">".into(),
 8826                            close: true,
 8827                            ..Default::default()
 8828                        },
 8829                        BracketPair {
 8830                            start: "{".into(),
 8831                            end: "}".into(),
 8832                            close: true,
 8833                            ..Default::default()
 8834                        },
 8835                        BracketPair {
 8836                            start: "(".into(),
 8837                            end: ")".into(),
 8838                            close: true,
 8839                            ..Default::default()
 8840                        },
 8841                    ],
 8842                    ..Default::default()
 8843                },
 8844                autoclose_before: "})]>".into(),
 8845                ..Default::default()
 8846            },
 8847            Some(tree_sitter_html::LANGUAGE.into()),
 8848        )
 8849        .with_injection_query(
 8850            r#"
 8851            (script_element
 8852                (raw_text) @injection.content
 8853                (#set! injection.language "javascript"))
 8854            "#,
 8855        )
 8856        .unwrap(),
 8857    );
 8858
 8859    let javascript_language = Arc::new(Language::new(
 8860        LanguageConfig {
 8861            name: "JavaScript".into(),
 8862            brackets: BracketPairConfig {
 8863                pairs: vec![
 8864                    BracketPair {
 8865                        start: "/*".into(),
 8866                        end: " */".into(),
 8867                        close: true,
 8868                        ..Default::default()
 8869                    },
 8870                    BracketPair {
 8871                        start: "{".into(),
 8872                        end: "}".into(),
 8873                        close: true,
 8874                        ..Default::default()
 8875                    },
 8876                    BracketPair {
 8877                        start: "(".into(),
 8878                        end: ")".into(),
 8879                        close: true,
 8880                        ..Default::default()
 8881                    },
 8882                ],
 8883                ..Default::default()
 8884            },
 8885            autoclose_before: "})]>".into(),
 8886            ..Default::default()
 8887        },
 8888        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8889    ));
 8890
 8891    cx.language_registry().add(html_language.clone());
 8892    cx.language_registry().add(javascript_language);
 8893    cx.executor().run_until_parked();
 8894
 8895    cx.update_buffer(|buffer, cx| {
 8896        buffer.set_language(Some(html_language), cx);
 8897    });
 8898
 8899    cx.set_state(
 8900        &r#"
 8901            <body>ˇ
 8902                <script>
 8903                    var x = 1;ˇ
 8904                </script>
 8905            </body>ˇ
 8906        "#
 8907        .unindent(),
 8908    );
 8909
 8910    // Precondition: different languages are active at different locations.
 8911    cx.update_editor(|editor, window, cx| {
 8912        let snapshot = editor.snapshot(window, cx);
 8913        let cursors = editor.selections.ranges::<usize>(cx);
 8914        let languages = cursors
 8915            .iter()
 8916            .map(|c| snapshot.language_at(c.start).unwrap().name())
 8917            .collect::<Vec<_>>();
 8918        assert_eq!(
 8919            languages,
 8920            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 8921        );
 8922    });
 8923
 8924    // Angle brackets autoclose in HTML, but not JavaScript.
 8925    cx.update_editor(|editor, window, cx| {
 8926        editor.handle_input("<", window, cx);
 8927        editor.handle_input("a", window, cx);
 8928    });
 8929    cx.assert_editor_state(
 8930        &r#"
 8931            <body><aˇ>
 8932                <script>
 8933                    var x = 1;<aˇ
 8934                </script>
 8935            </body><aˇ>
 8936        "#
 8937        .unindent(),
 8938    );
 8939
 8940    // Curly braces and parens autoclose in both HTML and JavaScript.
 8941    cx.update_editor(|editor, window, cx| {
 8942        editor.handle_input(" b=", window, cx);
 8943        editor.handle_input("{", window, cx);
 8944        editor.handle_input("c", window, cx);
 8945        editor.handle_input("(", window, cx);
 8946    });
 8947    cx.assert_editor_state(
 8948        &r#"
 8949            <body><a b={c(ˇ)}>
 8950                <script>
 8951                    var x = 1;<a b={c(ˇ)}
 8952                </script>
 8953            </body><a b={c(ˇ)}>
 8954        "#
 8955        .unindent(),
 8956    );
 8957
 8958    // Brackets that were already autoclosed are skipped.
 8959    cx.update_editor(|editor, window, cx| {
 8960        editor.handle_input(")", window, cx);
 8961        editor.handle_input("d", window, cx);
 8962        editor.handle_input("}", window, cx);
 8963    });
 8964    cx.assert_editor_state(
 8965        &r#"
 8966            <body><a b={c()d}ˇ>
 8967                <script>
 8968                    var x = 1;<a b={c()d}ˇ
 8969                </script>
 8970            </body><a b={c()d}ˇ>
 8971        "#
 8972        .unindent(),
 8973    );
 8974    cx.update_editor(|editor, window, cx| {
 8975        editor.handle_input(">", window, cx);
 8976    });
 8977    cx.assert_editor_state(
 8978        &r#"
 8979            <body><a b={c()d}>ˇ
 8980                <script>
 8981                    var x = 1;<a b={c()d}>ˇ
 8982                </script>
 8983            </body><a b={c()d}>ˇ
 8984        "#
 8985        .unindent(),
 8986    );
 8987
 8988    // Reset
 8989    cx.set_state(
 8990        &r#"
 8991            <body>ˇ
 8992                <script>
 8993                    var x = 1;ˇ
 8994                </script>
 8995            </body>ˇ
 8996        "#
 8997        .unindent(),
 8998    );
 8999
 9000    cx.update_editor(|editor, window, cx| {
 9001        editor.handle_input("<", window, cx);
 9002    });
 9003    cx.assert_editor_state(
 9004        &r#"
 9005            <body><ˇ>
 9006                <script>
 9007                    var x = 1;<ˇ
 9008                </script>
 9009            </body><ˇ>
 9010        "#
 9011        .unindent(),
 9012    );
 9013
 9014    // When backspacing, the closing angle brackets are removed.
 9015    cx.update_editor(|editor, window, cx| {
 9016        editor.backspace(&Backspace, window, cx);
 9017    });
 9018    cx.assert_editor_state(
 9019        &r#"
 9020            <body>ˇ
 9021                <script>
 9022                    var x = 1;ˇ
 9023                </script>
 9024            </body>ˇ
 9025        "#
 9026        .unindent(),
 9027    );
 9028
 9029    // Block comments autoclose in JavaScript, but not HTML.
 9030    cx.update_editor(|editor, window, cx| {
 9031        editor.handle_input("/", window, cx);
 9032        editor.handle_input("*", window, cx);
 9033    });
 9034    cx.assert_editor_state(
 9035        &r#"
 9036            <body>/*ˇ
 9037                <script>
 9038                    var x = 1;/*ˇ */
 9039                </script>
 9040            </body>/*ˇ
 9041        "#
 9042        .unindent(),
 9043    );
 9044}
 9045
 9046#[gpui::test]
 9047async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9048    init_test(cx, |_| {});
 9049
 9050    let mut cx = EditorTestContext::new(cx).await;
 9051
 9052    let rust_language = Arc::new(
 9053        Language::new(
 9054            LanguageConfig {
 9055                name: "Rust".into(),
 9056                brackets: serde_json::from_value(json!([
 9057                    { "start": "{", "end": "}", "close": true, "newline": true },
 9058                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9059                ]))
 9060                .unwrap(),
 9061                autoclose_before: "})]>".into(),
 9062                ..Default::default()
 9063            },
 9064            Some(tree_sitter_rust::LANGUAGE.into()),
 9065        )
 9066        .with_override_query("(string_literal) @string")
 9067        .unwrap(),
 9068    );
 9069
 9070    cx.language_registry().add(rust_language.clone());
 9071    cx.update_buffer(|buffer, cx| {
 9072        buffer.set_language(Some(rust_language), cx);
 9073    });
 9074
 9075    cx.set_state(
 9076        &r#"
 9077            let x = ˇ
 9078        "#
 9079        .unindent(),
 9080    );
 9081
 9082    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9083    cx.update_editor(|editor, window, cx| {
 9084        editor.handle_input("\"", window, cx);
 9085    });
 9086    cx.assert_editor_state(
 9087        &r#"
 9088            let x = "ˇ"
 9089        "#
 9090        .unindent(),
 9091    );
 9092
 9093    // Inserting another quotation mark. The cursor moves across the existing
 9094    // automatically-inserted quotation mark.
 9095    cx.update_editor(|editor, window, cx| {
 9096        editor.handle_input("\"", window, cx);
 9097    });
 9098    cx.assert_editor_state(
 9099        &r#"
 9100            let x = ""ˇ
 9101        "#
 9102        .unindent(),
 9103    );
 9104
 9105    // Reset
 9106    cx.set_state(
 9107        &r#"
 9108            let x = ˇ
 9109        "#
 9110        .unindent(),
 9111    );
 9112
 9113    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9114    cx.update_editor(|editor, window, cx| {
 9115        editor.handle_input("\"", window, cx);
 9116        editor.handle_input(" ", window, cx);
 9117        editor.move_left(&Default::default(), window, cx);
 9118        editor.handle_input("\\", window, cx);
 9119        editor.handle_input("\"", window, cx);
 9120    });
 9121    cx.assert_editor_state(
 9122        &r#"
 9123            let x = "\"ˇ "
 9124        "#
 9125        .unindent(),
 9126    );
 9127
 9128    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9129    // mark. Nothing is inserted.
 9130    cx.update_editor(|editor, window, cx| {
 9131        editor.move_right(&Default::default(), window, cx);
 9132        editor.handle_input("\"", window, cx);
 9133    });
 9134    cx.assert_editor_state(
 9135        &r#"
 9136            let x = "\" "ˇ
 9137        "#
 9138        .unindent(),
 9139    );
 9140}
 9141
 9142#[gpui::test]
 9143async fn test_surround_with_pair(cx: &mut TestAppContext) {
 9144    init_test(cx, |_| {});
 9145
 9146    let language = Arc::new(Language::new(
 9147        LanguageConfig {
 9148            brackets: BracketPairConfig {
 9149                pairs: vec![
 9150                    BracketPair {
 9151                        start: "{".to_string(),
 9152                        end: "}".to_string(),
 9153                        close: true,
 9154                        surround: true,
 9155                        newline: true,
 9156                    },
 9157                    BracketPair {
 9158                        start: "/* ".to_string(),
 9159                        end: "*/".to_string(),
 9160                        close: true,
 9161                        surround: true,
 9162                        ..Default::default()
 9163                    },
 9164                ],
 9165                ..Default::default()
 9166            },
 9167            ..Default::default()
 9168        },
 9169        Some(tree_sitter_rust::LANGUAGE.into()),
 9170    ));
 9171
 9172    let text = r#"
 9173        a
 9174        b
 9175        c
 9176    "#
 9177    .unindent();
 9178
 9179    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9180    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9181    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9182    editor
 9183        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9184        .await;
 9185
 9186    editor.update_in(cx, |editor, window, cx| {
 9187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9188            s.select_display_ranges([
 9189                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9190                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9191                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 9192            ])
 9193        });
 9194
 9195        editor.handle_input("{", window, cx);
 9196        editor.handle_input("{", window, cx);
 9197        editor.handle_input("{", window, cx);
 9198        assert_eq!(
 9199            editor.text(cx),
 9200            "
 9201                {{{a}}}
 9202                {{{b}}}
 9203                {{{c}}}
 9204            "
 9205            .unindent()
 9206        );
 9207        assert_eq!(
 9208            editor.selections.display_ranges(cx),
 9209            [
 9210                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9211                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9212                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9213            ]
 9214        );
 9215
 9216        editor.undo(&Undo, window, cx);
 9217        editor.undo(&Undo, window, cx);
 9218        editor.undo(&Undo, window, cx);
 9219        assert_eq!(
 9220            editor.text(cx),
 9221            "
 9222                a
 9223                b
 9224                c
 9225            "
 9226            .unindent()
 9227        );
 9228        assert_eq!(
 9229            editor.selections.display_ranges(cx),
 9230            [
 9231                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9232                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9233                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9234            ]
 9235        );
 9236
 9237        // Ensure inserting the first character of a multi-byte bracket pair
 9238        // doesn't surround the selections with the bracket.
 9239        editor.handle_input("/", window, cx);
 9240        assert_eq!(
 9241            editor.text(cx),
 9242            "
 9243                /
 9244                /
 9245                /
 9246            "
 9247            .unindent()
 9248        );
 9249        assert_eq!(
 9250            editor.selections.display_ranges(cx),
 9251            [
 9252                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9253                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9254                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9255            ]
 9256        );
 9257
 9258        editor.undo(&Undo, window, cx);
 9259        assert_eq!(
 9260            editor.text(cx),
 9261            "
 9262                a
 9263                b
 9264                c
 9265            "
 9266            .unindent()
 9267        );
 9268        assert_eq!(
 9269            editor.selections.display_ranges(cx),
 9270            [
 9271                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9272                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9273                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9274            ]
 9275        );
 9276
 9277        // Ensure inserting the last character of a multi-byte bracket pair
 9278        // doesn't surround the selections with the bracket.
 9279        editor.handle_input("*", window, cx);
 9280        assert_eq!(
 9281            editor.text(cx),
 9282            "
 9283                *
 9284                *
 9285                *
 9286            "
 9287            .unindent()
 9288        );
 9289        assert_eq!(
 9290            editor.selections.display_ranges(cx),
 9291            [
 9292                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9293                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9294                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9295            ]
 9296        );
 9297    });
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    let language = Arc::new(Language::new(
 9305        LanguageConfig {
 9306            brackets: BracketPairConfig {
 9307                pairs: vec![BracketPair {
 9308                    start: "{".to_string(),
 9309                    end: "}".to_string(),
 9310                    close: true,
 9311                    surround: true,
 9312                    newline: true,
 9313                }],
 9314                ..Default::default()
 9315            },
 9316            autoclose_before: "}".to_string(),
 9317            ..Default::default()
 9318        },
 9319        Some(tree_sitter_rust::LANGUAGE.into()),
 9320    ));
 9321
 9322    let text = r#"
 9323        a
 9324        b
 9325        c
 9326    "#
 9327    .unindent();
 9328
 9329    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9330    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9331    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9332    editor
 9333        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9334        .await;
 9335
 9336    editor.update_in(cx, |editor, window, cx| {
 9337        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9338            s.select_ranges([
 9339                Point::new(0, 1)..Point::new(0, 1),
 9340                Point::new(1, 1)..Point::new(1, 1),
 9341                Point::new(2, 1)..Point::new(2, 1),
 9342            ])
 9343        });
 9344
 9345        editor.handle_input("{", window, cx);
 9346        editor.handle_input("{", window, cx);
 9347        editor.handle_input("_", window, cx);
 9348        assert_eq!(
 9349            editor.text(cx),
 9350            "
 9351                a{{_}}
 9352                b{{_}}
 9353                c{{_}}
 9354            "
 9355            .unindent()
 9356        );
 9357        assert_eq!(
 9358            editor.selections.ranges::<Point>(cx),
 9359            [
 9360                Point::new(0, 4)..Point::new(0, 4),
 9361                Point::new(1, 4)..Point::new(1, 4),
 9362                Point::new(2, 4)..Point::new(2, 4)
 9363            ]
 9364        );
 9365
 9366        editor.backspace(&Default::default(), window, cx);
 9367        editor.backspace(&Default::default(), window, cx);
 9368        assert_eq!(
 9369            editor.text(cx),
 9370            "
 9371                a{}
 9372                b{}
 9373                c{}
 9374            "
 9375            .unindent()
 9376        );
 9377        assert_eq!(
 9378            editor.selections.ranges::<Point>(cx),
 9379            [
 9380                Point::new(0, 2)..Point::new(0, 2),
 9381                Point::new(1, 2)..Point::new(1, 2),
 9382                Point::new(2, 2)..Point::new(2, 2)
 9383            ]
 9384        );
 9385
 9386        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9387        assert_eq!(
 9388            editor.text(cx),
 9389            "
 9390                a
 9391                b
 9392                c
 9393            "
 9394            .unindent()
 9395        );
 9396        assert_eq!(
 9397            editor.selections.ranges::<Point>(cx),
 9398            [
 9399                Point::new(0, 1)..Point::new(0, 1),
 9400                Point::new(1, 1)..Point::new(1, 1),
 9401                Point::new(2, 1)..Point::new(2, 1)
 9402            ]
 9403        );
 9404    });
 9405}
 9406
 9407#[gpui::test]
 9408async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9409    init_test(cx, |settings| {
 9410        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9411    });
 9412
 9413    let mut cx = EditorTestContext::new(cx).await;
 9414
 9415    let language = Arc::new(Language::new(
 9416        LanguageConfig {
 9417            brackets: BracketPairConfig {
 9418                pairs: vec![
 9419                    BracketPair {
 9420                        start: "{".to_string(),
 9421                        end: "}".to_string(),
 9422                        close: true,
 9423                        surround: true,
 9424                        newline: true,
 9425                    },
 9426                    BracketPair {
 9427                        start: "(".to_string(),
 9428                        end: ")".to_string(),
 9429                        close: true,
 9430                        surround: true,
 9431                        newline: true,
 9432                    },
 9433                    BracketPair {
 9434                        start: "[".to_string(),
 9435                        end: "]".to_string(),
 9436                        close: false,
 9437                        surround: true,
 9438                        newline: true,
 9439                    },
 9440                ],
 9441                ..Default::default()
 9442            },
 9443            autoclose_before: "})]".to_string(),
 9444            ..Default::default()
 9445        },
 9446        Some(tree_sitter_rust::LANGUAGE.into()),
 9447    ));
 9448
 9449    cx.language_registry().add(language.clone());
 9450    cx.update_buffer(|buffer, cx| {
 9451        buffer.set_language(Some(language), cx);
 9452    });
 9453
 9454    cx.set_state(
 9455        &"
 9456            {(ˇ)}
 9457            [[ˇ]]
 9458            {(ˇ)}
 9459        "
 9460        .unindent(),
 9461    );
 9462
 9463    cx.update_editor(|editor, window, cx| {
 9464        editor.backspace(&Default::default(), window, cx);
 9465        editor.backspace(&Default::default(), window, cx);
 9466    });
 9467
 9468    cx.assert_editor_state(
 9469        &"
 9470            ˇ
 9471            ˇ]]
 9472            ˇ
 9473        "
 9474        .unindent(),
 9475    );
 9476
 9477    cx.update_editor(|editor, window, cx| {
 9478        editor.handle_input("{", window, cx);
 9479        editor.handle_input("{", window, cx);
 9480        editor.move_right(&MoveRight, window, cx);
 9481        editor.move_right(&MoveRight, window, cx);
 9482        editor.move_left(&MoveLeft, window, cx);
 9483        editor.move_left(&MoveLeft, window, cx);
 9484        editor.backspace(&Default::default(), window, cx);
 9485    });
 9486
 9487    cx.assert_editor_state(
 9488        &"
 9489            {ˇ}
 9490            {ˇ}]]
 9491            {ˇ}
 9492        "
 9493        .unindent(),
 9494    );
 9495
 9496    cx.update_editor(|editor, window, cx| {
 9497        editor.backspace(&Default::default(), window, cx);
 9498    });
 9499
 9500    cx.assert_editor_state(
 9501        &"
 9502            ˇ
 9503            ˇ]]
 9504            ˇ
 9505        "
 9506        .unindent(),
 9507    );
 9508}
 9509
 9510#[gpui::test]
 9511async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9512    init_test(cx, |_| {});
 9513
 9514    let language = Arc::new(Language::new(
 9515        LanguageConfig::default(),
 9516        Some(tree_sitter_rust::LANGUAGE.into()),
 9517    ));
 9518
 9519    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9520    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9521    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9522    editor
 9523        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9524        .await;
 9525
 9526    editor.update_in(cx, |editor, window, cx| {
 9527        editor.set_auto_replace_emoji_shortcode(true);
 9528
 9529        editor.handle_input("Hello ", window, cx);
 9530        editor.handle_input(":wave", window, cx);
 9531        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9532
 9533        editor.handle_input(":", window, cx);
 9534        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9535
 9536        editor.handle_input(" :smile", window, cx);
 9537        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9538
 9539        editor.handle_input(":", window, cx);
 9540        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9541
 9542        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9543        editor.handle_input(":wave", window, cx);
 9544        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9545
 9546        editor.handle_input(":", window, cx);
 9547        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9548
 9549        editor.handle_input(":1", window, cx);
 9550        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9551
 9552        editor.handle_input(":", window, cx);
 9553        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9554
 9555        // Ensure shortcode does not get replaced when it is part of a word
 9556        editor.handle_input(" Test:wave", window, cx);
 9557        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9558
 9559        editor.handle_input(":", window, cx);
 9560        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9561
 9562        editor.set_auto_replace_emoji_shortcode(false);
 9563
 9564        // Ensure shortcode does not get replaced when auto replace is off
 9565        editor.handle_input(" :wave", window, cx);
 9566        assert_eq!(
 9567            editor.text(cx),
 9568            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9569        );
 9570
 9571        editor.handle_input(":", window, cx);
 9572        assert_eq!(
 9573            editor.text(cx),
 9574            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9575        );
 9576    });
 9577}
 9578
 9579#[gpui::test]
 9580async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9581    init_test(cx, |_| {});
 9582
 9583    let (text, insertion_ranges) = marked_text_ranges(
 9584        indoc! {"
 9585            ˇ
 9586        "},
 9587        false,
 9588    );
 9589
 9590    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9591    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9592
 9593    _ = editor.update_in(cx, |editor, window, cx| {
 9594        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9595
 9596        editor
 9597            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9598            .unwrap();
 9599
 9600        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9601            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9602            assert_eq!(editor.text(cx), expected_text);
 9603            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9604        }
 9605
 9606        assert(
 9607            editor,
 9608            cx,
 9609            indoc! {"
 9610            type «» =•
 9611            "},
 9612        );
 9613
 9614        assert!(editor.context_menu_visible(), "There should be a matches");
 9615    });
 9616}
 9617
 9618#[gpui::test]
 9619async fn test_snippets(cx: &mut TestAppContext) {
 9620    init_test(cx, |_| {});
 9621
 9622    let mut cx = EditorTestContext::new(cx).await;
 9623
 9624    cx.set_state(indoc! {"
 9625        a.ˇ b
 9626        a.ˇ b
 9627        a.ˇ b
 9628    "});
 9629
 9630    cx.update_editor(|editor, window, cx| {
 9631        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9632        let insertion_ranges = editor
 9633            .selections
 9634            .all(cx)
 9635            .iter()
 9636            .map(|s| s.range())
 9637            .collect::<Vec<_>>();
 9638        editor
 9639            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9640            .unwrap();
 9641    });
 9642
 9643    cx.assert_editor_state(indoc! {"
 9644        a.f(«oneˇ», two, «threeˇ») b
 9645        a.f(«oneˇ», two, «threeˇ») b
 9646        a.f(«oneˇ», two, «threeˇ») b
 9647    "});
 9648
 9649    // Can't move earlier than the first tab stop
 9650    cx.update_editor(|editor, window, cx| {
 9651        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9652    });
 9653    cx.assert_editor_state(indoc! {"
 9654        a.f(«oneˇ», two, «threeˇ») b
 9655        a.f(«oneˇ», two, «threeˇ») b
 9656        a.f(«oneˇ», two, «threeˇ») b
 9657    "});
 9658
 9659    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9660    cx.assert_editor_state(indoc! {"
 9661        a.f(one, «twoˇ», three) b
 9662        a.f(one, «twoˇ», three) b
 9663        a.f(one, «twoˇ», three) b
 9664    "});
 9665
 9666    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9667    cx.assert_editor_state(indoc! {"
 9668        a.f(«oneˇ», two, «threeˇ») b
 9669        a.f(«oneˇ», two, «threeˇ») b
 9670        a.f(«oneˇ», two, «threeˇ») b
 9671    "});
 9672
 9673    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9674    cx.assert_editor_state(indoc! {"
 9675        a.f(one, «twoˇ», three) b
 9676        a.f(one, «twoˇ», three) b
 9677        a.f(one, «twoˇ», three) b
 9678    "});
 9679    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9680    cx.assert_editor_state(indoc! {"
 9681        a.f(one, two, three)ˇ b
 9682        a.f(one, two, three)ˇ b
 9683        a.f(one, two, three)ˇ b
 9684    "});
 9685
 9686    // As soon as the last tab stop is reached, snippet state is gone
 9687    cx.update_editor(|editor, window, cx| {
 9688        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9689    });
 9690    cx.assert_editor_state(indoc! {"
 9691        a.f(one, two, three)ˇ b
 9692        a.f(one, two, three)ˇ b
 9693        a.f(one, two, three)ˇ b
 9694    "});
 9695}
 9696
 9697#[gpui::test]
 9698async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9699    init_test(cx, |_| {});
 9700
 9701    let mut cx = EditorTestContext::new(cx).await;
 9702
 9703    cx.update_editor(|editor, window, cx| {
 9704        let snippet = Snippet::parse(indoc! {"
 9705            /*
 9706             * Multiline comment with leading indentation
 9707             *
 9708             * $1
 9709             */
 9710            $0"})
 9711        .unwrap();
 9712        let insertion_ranges = editor
 9713            .selections
 9714            .all(cx)
 9715            .iter()
 9716            .map(|s| s.range())
 9717            .collect::<Vec<_>>();
 9718        editor
 9719            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9720            .unwrap();
 9721    });
 9722
 9723    cx.assert_editor_state(indoc! {"
 9724        /*
 9725         * Multiline comment with leading indentation
 9726         *
 9727         * ˇ
 9728         */
 9729    "});
 9730
 9731    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9732    cx.assert_editor_state(indoc! {"
 9733        /*
 9734         * Multiline comment with leading indentation
 9735         *
 9736         *•
 9737         */
 9738        ˇ"});
 9739}
 9740
 9741#[gpui::test]
 9742async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9743    init_test(cx, |_| {});
 9744
 9745    let fs = FakeFs::new(cx.executor());
 9746    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9747
 9748    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9749
 9750    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9751    language_registry.add(rust_lang());
 9752    let mut fake_servers = language_registry.register_fake_lsp(
 9753        "Rust",
 9754        FakeLspAdapter {
 9755            capabilities: lsp::ServerCapabilities {
 9756                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9757                ..Default::default()
 9758            },
 9759            ..Default::default()
 9760        },
 9761    );
 9762
 9763    let buffer = project
 9764        .update(cx, |project, cx| {
 9765            project.open_local_buffer(path!("/file.rs"), cx)
 9766        })
 9767        .await
 9768        .unwrap();
 9769
 9770    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9771    let (editor, cx) = cx.add_window_view(|window, cx| {
 9772        build_editor_with_project(project.clone(), buffer, window, cx)
 9773    });
 9774    editor.update_in(cx, |editor, window, cx| {
 9775        editor.set_text("one\ntwo\nthree\n", window, cx)
 9776    });
 9777    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9778
 9779    cx.executor().start_waiting();
 9780    let fake_server = fake_servers.next().await.unwrap();
 9781
 9782    {
 9783        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9784            move |params, _| async move {
 9785                assert_eq!(
 9786                    params.text_document.uri,
 9787                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9788                );
 9789                assert_eq!(params.options.tab_size, 4);
 9790                Ok(Some(vec![lsp::TextEdit::new(
 9791                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9792                    ", ".to_string(),
 9793                )]))
 9794            },
 9795        );
 9796        let save = editor
 9797            .update_in(cx, |editor, window, cx| {
 9798                editor.save(
 9799                    SaveOptions {
 9800                        format: true,
 9801                        autosave: false,
 9802                    },
 9803                    project.clone(),
 9804                    window,
 9805                    cx,
 9806                )
 9807            })
 9808            .unwrap();
 9809        cx.executor().start_waiting();
 9810        save.await;
 9811
 9812        assert_eq!(
 9813            editor.update(cx, |editor, cx| editor.text(cx)),
 9814            "one, two\nthree\n"
 9815        );
 9816        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9817    }
 9818
 9819    {
 9820        editor.update_in(cx, |editor, window, cx| {
 9821            editor.set_text("one\ntwo\nthree\n", window, cx)
 9822        });
 9823        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9824
 9825        // Ensure we can still save even if formatting hangs.
 9826        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9827            move |params, _| async move {
 9828                assert_eq!(
 9829                    params.text_document.uri,
 9830                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9831                );
 9832                futures::future::pending::<()>().await;
 9833                unreachable!()
 9834            },
 9835        );
 9836        let save = editor
 9837            .update_in(cx, |editor, window, cx| {
 9838                editor.save(
 9839                    SaveOptions {
 9840                        format: true,
 9841                        autosave: false,
 9842                    },
 9843                    project.clone(),
 9844                    window,
 9845                    cx,
 9846                )
 9847            })
 9848            .unwrap();
 9849        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9850        cx.executor().start_waiting();
 9851        save.await;
 9852        assert_eq!(
 9853            editor.update(cx, |editor, cx| editor.text(cx)),
 9854            "one\ntwo\nthree\n"
 9855        );
 9856    }
 9857
 9858    // Set rust language override and assert overridden tabsize is sent to language server
 9859    update_test_language_settings(cx, |settings| {
 9860        settings.languages.0.insert(
 9861            "Rust".into(),
 9862            LanguageSettingsContent {
 9863                tab_size: NonZeroU32::new(8),
 9864                ..Default::default()
 9865            },
 9866        );
 9867    });
 9868
 9869    {
 9870        editor.update_in(cx, |editor, window, cx| {
 9871            editor.set_text("somehting_new\n", window, cx)
 9872        });
 9873        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9874        let _formatting_request_signal = fake_server
 9875            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
 9876                assert_eq!(
 9877                    params.text_document.uri,
 9878                    lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 9879                );
 9880                assert_eq!(params.options.tab_size, 8);
 9881                Ok(Some(vec![]))
 9882            });
 9883        let save = editor
 9884            .update_in(cx, |editor, window, cx| {
 9885                editor.save(
 9886                    SaveOptions {
 9887                        format: true,
 9888                        autosave: false,
 9889                    },
 9890                    project.clone(),
 9891                    window,
 9892                    cx,
 9893                )
 9894            })
 9895            .unwrap();
 9896        cx.executor().start_waiting();
 9897        save.await;
 9898    }
 9899}
 9900
 9901#[gpui::test]
 9902async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
 9903    init_test(cx, |settings| {
 9904        settings.defaults.ensure_final_newline_on_save = Some(false);
 9905    });
 9906
 9907    let fs = FakeFs::new(cx.executor());
 9908    fs.insert_file(path!("/file.txt"), "foo".into()).await;
 9909
 9910    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 9911
 9912    let buffer = project
 9913        .update(cx, |project, cx| {
 9914            project.open_local_buffer(path!("/file.txt"), cx)
 9915        })
 9916        .await
 9917        .unwrap();
 9918
 9919    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9920    let (editor, cx) = cx.add_window_view(|window, cx| {
 9921        build_editor_with_project(project.clone(), buffer, window, cx)
 9922    });
 9923    editor.update_in(cx, |editor, window, cx| {
 9924        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9925            s.select_ranges([0..0])
 9926        });
 9927    });
 9928    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9929
 9930    editor.update_in(cx, |editor, window, cx| {
 9931        editor.handle_input("\n", window, cx)
 9932    });
 9933    cx.run_until_parked();
 9934    save(&editor, &project, cx).await;
 9935    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9936
 9937    editor.update_in(cx, |editor, window, cx| {
 9938        editor.undo(&Default::default(), window, cx);
 9939    });
 9940    save(&editor, &project, cx).await;
 9941    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9942
 9943    editor.update_in(cx, |editor, window, cx| {
 9944        editor.redo(&Default::default(), window, cx);
 9945    });
 9946    cx.run_until_parked();
 9947    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
 9948
 9949    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
 9950        let save = editor
 9951            .update_in(cx, |editor, window, cx| {
 9952                editor.save(
 9953                    SaveOptions {
 9954                        format: true,
 9955                        autosave: false,
 9956                    },
 9957                    project.clone(),
 9958                    window,
 9959                    cx,
 9960                )
 9961            })
 9962            .unwrap();
 9963        cx.executor().start_waiting();
 9964        save.await;
 9965        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9966    }
 9967}
 9968
 9969#[gpui::test]
 9970async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 9971    init_test(cx, |_| {});
 9972
 9973    let cols = 4;
 9974    let rows = 10;
 9975    let sample_text_1 = sample_text(rows, cols, 'a');
 9976    assert_eq!(
 9977        sample_text_1,
 9978        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 9979    );
 9980    let sample_text_2 = sample_text(rows, cols, 'l');
 9981    assert_eq!(
 9982        sample_text_2,
 9983        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 9984    );
 9985    let sample_text_3 = sample_text(rows, cols, 'v');
 9986    assert_eq!(
 9987        sample_text_3,
 9988        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 9989    );
 9990
 9991    let fs = FakeFs::new(cx.executor());
 9992    fs.insert_tree(
 9993        path!("/a"),
 9994        json!({
 9995            "main.rs": sample_text_1,
 9996            "other.rs": sample_text_2,
 9997            "lib.rs": sample_text_3,
 9998        }),
 9999    )
10000    .await;
10001
10002    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10003    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10004    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10005
10006    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10007    language_registry.add(rust_lang());
10008    let mut fake_servers = language_registry.register_fake_lsp(
10009        "Rust",
10010        FakeLspAdapter {
10011            capabilities: lsp::ServerCapabilities {
10012                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10013                ..Default::default()
10014            },
10015            ..Default::default()
10016        },
10017    );
10018
10019    let worktree = project.update(cx, |project, cx| {
10020        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10021        assert_eq!(worktrees.len(), 1);
10022        worktrees.pop().unwrap()
10023    });
10024    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10025
10026    let buffer_1 = project
10027        .update(cx, |project, cx| {
10028            project.open_buffer((worktree_id, "main.rs"), cx)
10029        })
10030        .await
10031        .unwrap();
10032    let buffer_2 = project
10033        .update(cx, |project, cx| {
10034            project.open_buffer((worktree_id, "other.rs"), cx)
10035        })
10036        .await
10037        .unwrap();
10038    let buffer_3 = project
10039        .update(cx, |project, cx| {
10040            project.open_buffer((worktree_id, "lib.rs"), cx)
10041        })
10042        .await
10043        .unwrap();
10044
10045    let multi_buffer = cx.new(|cx| {
10046        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10047        multi_buffer.push_excerpts(
10048            buffer_1.clone(),
10049            [
10050                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10051                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10052                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10053            ],
10054            cx,
10055        );
10056        multi_buffer.push_excerpts(
10057            buffer_2.clone(),
10058            [
10059                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10060                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10061                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10062            ],
10063            cx,
10064        );
10065        multi_buffer.push_excerpts(
10066            buffer_3.clone(),
10067            [
10068                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10069                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10070                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10071            ],
10072            cx,
10073        );
10074        multi_buffer
10075    });
10076    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10077        Editor::new(
10078            EditorMode::full(),
10079            multi_buffer,
10080            Some(project.clone()),
10081            window,
10082            cx,
10083        )
10084    });
10085
10086    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10087        editor.change_selections(
10088            SelectionEffects::scroll(Autoscroll::Next),
10089            window,
10090            cx,
10091            |s| s.select_ranges(Some(1..2)),
10092        );
10093        editor.insert("|one|two|three|", window, cx);
10094    });
10095    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10096    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10097        editor.change_selections(
10098            SelectionEffects::scroll(Autoscroll::Next),
10099            window,
10100            cx,
10101            |s| s.select_ranges(Some(60..70)),
10102        );
10103        editor.insert("|four|five|six|", window, cx);
10104    });
10105    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10106
10107    // First two buffers should be edited, but not the third one.
10108    assert_eq!(
10109        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10110        "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}",
10111    );
10112    buffer_1.update(cx, |buffer, _| {
10113        assert!(buffer.is_dirty());
10114        assert_eq!(
10115            buffer.text(),
10116            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10117        )
10118    });
10119    buffer_2.update(cx, |buffer, _| {
10120        assert!(buffer.is_dirty());
10121        assert_eq!(
10122            buffer.text(),
10123            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10124        )
10125    });
10126    buffer_3.update(cx, |buffer, _| {
10127        assert!(!buffer.is_dirty());
10128        assert_eq!(buffer.text(), sample_text_3,)
10129    });
10130    cx.executor().run_until_parked();
10131
10132    cx.executor().start_waiting();
10133    let save = multi_buffer_editor
10134        .update_in(cx, |editor, window, cx| {
10135            editor.save(
10136                SaveOptions {
10137                    format: true,
10138                    autosave: false,
10139                },
10140                project.clone(),
10141                window,
10142                cx,
10143            )
10144        })
10145        .unwrap();
10146
10147    let fake_server = fake_servers.next().await.unwrap();
10148    fake_server
10149        .server
10150        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
10151            Ok(Some(vec![lsp::TextEdit::new(
10152                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10153                format!("[{} formatted]", params.text_document.uri),
10154            )]))
10155        })
10156        .detach();
10157    save.await;
10158
10159    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
10160    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
10161    assert_eq!(
10162        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10163        uri!(
10164            "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}"
10165        ),
10166    );
10167    buffer_1.update(cx, |buffer, _| {
10168        assert!(!buffer.is_dirty());
10169        assert_eq!(
10170            buffer.text(),
10171            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
10172        )
10173    });
10174    buffer_2.update(cx, |buffer, _| {
10175        assert!(!buffer.is_dirty());
10176        assert_eq!(
10177            buffer.text(),
10178            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
10179        )
10180    });
10181    buffer_3.update(cx, |buffer, _| {
10182        assert!(!buffer.is_dirty());
10183        assert_eq!(buffer.text(), sample_text_3,)
10184    });
10185}
10186
10187#[gpui::test]
10188async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
10189    init_test(cx, |_| {});
10190
10191    let fs = FakeFs::new(cx.executor());
10192    fs.insert_tree(
10193        path!("/dir"),
10194        json!({
10195            "file1.rs": "fn main() { println!(\"hello\"); }",
10196            "file2.rs": "fn test() { println!(\"test\"); }",
10197            "file3.rs": "fn other() { println!(\"other\"); }\n",
10198        }),
10199    )
10200    .await;
10201
10202    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10203    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10204    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10205
10206    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10207    language_registry.add(rust_lang());
10208
10209    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10210    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10211
10212    // Open three buffers
10213    let buffer_1 = project
10214        .update(cx, |project, cx| {
10215            project.open_buffer((worktree_id, "file1.rs"), cx)
10216        })
10217        .await
10218        .unwrap();
10219    let buffer_2 = project
10220        .update(cx, |project, cx| {
10221            project.open_buffer((worktree_id, "file2.rs"), cx)
10222        })
10223        .await
10224        .unwrap();
10225    let buffer_3 = project
10226        .update(cx, |project, cx| {
10227            project.open_buffer((worktree_id, "file3.rs"), cx)
10228        })
10229        .await
10230        .unwrap();
10231
10232    // Create a multi-buffer with all three buffers
10233    let multi_buffer = cx.new(|cx| {
10234        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10235        multi_buffer.push_excerpts(
10236            buffer_1.clone(),
10237            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10238            cx,
10239        );
10240        multi_buffer.push_excerpts(
10241            buffer_2.clone(),
10242            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10243            cx,
10244        );
10245        multi_buffer.push_excerpts(
10246            buffer_3.clone(),
10247            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10248            cx,
10249        );
10250        multi_buffer
10251    });
10252
10253    let editor = cx.new_window_entity(|window, cx| {
10254        Editor::new(
10255            EditorMode::full(),
10256            multi_buffer,
10257            Some(project.clone()),
10258            window,
10259            cx,
10260        )
10261    });
10262
10263    // Edit only the first buffer
10264    editor.update_in(cx, |editor, window, cx| {
10265        editor.change_selections(
10266            SelectionEffects::scroll(Autoscroll::Next),
10267            window,
10268            cx,
10269            |s| s.select_ranges(Some(10..10)),
10270        );
10271        editor.insert("// edited", window, cx);
10272    });
10273
10274    // Verify that only buffer 1 is dirty
10275    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10276    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10277    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10278
10279    // Get write counts after file creation (files were created with initial content)
10280    // We expect each file to have been written once during creation
10281    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10282    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10283    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10284
10285    // Perform autosave
10286    let save_task = editor.update_in(cx, |editor, window, cx| {
10287        editor.save(
10288            SaveOptions {
10289                format: true,
10290                autosave: true,
10291            },
10292            project.clone(),
10293            window,
10294            cx,
10295        )
10296    });
10297    save_task.await.unwrap();
10298
10299    // Only the dirty buffer should have been saved
10300    assert_eq!(
10301        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10302        1,
10303        "Buffer 1 was dirty, so it should have been written once during autosave"
10304    );
10305    assert_eq!(
10306        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10307        0,
10308        "Buffer 2 was clean, so it should not have been written during autosave"
10309    );
10310    assert_eq!(
10311        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10312        0,
10313        "Buffer 3 was clean, so it should not have been written during autosave"
10314    );
10315
10316    // Verify buffer states after autosave
10317    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10318    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10319    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10320
10321    // Now perform a manual save (format = true)
10322    let save_task = editor.update_in(cx, |editor, window, cx| {
10323        editor.save(
10324            SaveOptions {
10325                format: true,
10326                autosave: false,
10327            },
10328            project.clone(),
10329            window,
10330            cx,
10331        )
10332    });
10333    save_task.await.unwrap();
10334
10335    // During manual save, clean buffers don't get written to disk
10336    // They just get did_save called for language server notifications
10337    assert_eq!(
10338        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10339        1,
10340        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10341    );
10342    assert_eq!(
10343        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10344        0,
10345        "Buffer 2 should not have been written at all"
10346    );
10347    assert_eq!(
10348        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10349        0,
10350        "Buffer 3 should not have been written at all"
10351    );
10352}
10353
10354async fn setup_range_format_test(
10355    cx: &mut TestAppContext,
10356) -> (
10357    Entity<Project>,
10358    Entity<Editor>,
10359    &mut gpui::VisualTestContext,
10360    lsp::FakeLanguageServer,
10361) {
10362    init_test(cx, |_| {});
10363
10364    let fs = FakeFs::new(cx.executor());
10365    fs.insert_file(path!("/file.rs"), Default::default()).await;
10366
10367    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10368
10369    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10370    language_registry.add(rust_lang());
10371    let mut fake_servers = language_registry.register_fake_lsp(
10372        "Rust",
10373        FakeLspAdapter {
10374            capabilities: lsp::ServerCapabilities {
10375                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10376                ..lsp::ServerCapabilities::default()
10377            },
10378            ..FakeLspAdapter::default()
10379        },
10380    );
10381
10382    let buffer = project
10383        .update(cx, |project, cx| {
10384            project.open_local_buffer(path!("/file.rs"), cx)
10385        })
10386        .await
10387        .unwrap();
10388
10389    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10390    let (editor, cx) = cx.add_window_view(|window, cx| {
10391        build_editor_with_project(project.clone(), buffer, window, cx)
10392    });
10393
10394    cx.executor().start_waiting();
10395    let fake_server = fake_servers.next().await.unwrap();
10396
10397    (project, editor, cx, fake_server)
10398}
10399
10400#[gpui::test]
10401async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10402    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10403
10404    editor.update_in(cx, |editor, window, cx| {
10405        editor.set_text("one\ntwo\nthree\n", window, cx)
10406    });
10407    assert!(cx.read(|cx| editor.is_dirty(cx)));
10408
10409    let save = editor
10410        .update_in(cx, |editor, window, cx| {
10411            editor.save(
10412                SaveOptions {
10413                    format: true,
10414                    autosave: false,
10415                },
10416                project.clone(),
10417                window,
10418                cx,
10419            )
10420        })
10421        .unwrap();
10422    fake_server
10423        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10424            assert_eq!(
10425                params.text_document.uri,
10426                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10427            );
10428            assert_eq!(params.options.tab_size, 4);
10429            Ok(Some(vec![lsp::TextEdit::new(
10430                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10431                ", ".to_string(),
10432            )]))
10433        })
10434        .next()
10435        .await;
10436    cx.executor().start_waiting();
10437    save.await;
10438    assert_eq!(
10439        editor.update(cx, |editor, cx| editor.text(cx)),
10440        "one, two\nthree\n"
10441    );
10442    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10443}
10444
10445#[gpui::test]
10446async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10447    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10448
10449    editor.update_in(cx, |editor, window, cx| {
10450        editor.set_text("one\ntwo\nthree\n", window, cx)
10451    });
10452    assert!(cx.read(|cx| editor.is_dirty(cx)));
10453
10454    // Test that save still works when formatting hangs
10455    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10456        move |params, _| async move {
10457            assert_eq!(
10458                params.text_document.uri,
10459                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10460            );
10461            futures::future::pending::<()>().await;
10462            unreachable!()
10463        },
10464    );
10465    let save = editor
10466        .update_in(cx, |editor, window, cx| {
10467            editor.save(
10468                SaveOptions {
10469                    format: true,
10470                    autosave: false,
10471                },
10472                project.clone(),
10473                window,
10474                cx,
10475            )
10476        })
10477        .unwrap();
10478    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10479    cx.executor().start_waiting();
10480    save.await;
10481    assert_eq!(
10482        editor.update(cx, |editor, cx| editor.text(cx)),
10483        "one\ntwo\nthree\n"
10484    );
10485    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10486}
10487
10488#[gpui::test]
10489async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10490    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10491
10492    // Buffer starts clean, no formatting should be requested
10493    let save = editor
10494        .update_in(cx, |editor, window, cx| {
10495            editor.save(
10496                SaveOptions {
10497                    format: false,
10498                    autosave: false,
10499                },
10500                project.clone(),
10501                window,
10502                cx,
10503            )
10504        })
10505        .unwrap();
10506    let _pending_format_request = fake_server
10507        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10508            panic!("Should not be invoked");
10509        })
10510        .next();
10511    cx.executor().start_waiting();
10512    save.await;
10513    cx.run_until_parked();
10514}
10515
10516#[gpui::test]
10517async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10518    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10519
10520    // Set Rust language override and assert overridden tabsize is sent to language server
10521    update_test_language_settings(cx, |settings| {
10522        settings.languages.0.insert(
10523            "Rust".into(),
10524            LanguageSettingsContent {
10525                tab_size: NonZeroU32::new(8),
10526                ..Default::default()
10527            },
10528        );
10529    });
10530
10531    editor.update_in(cx, |editor, window, cx| {
10532        editor.set_text("something_new\n", window, cx)
10533    });
10534    assert!(cx.read(|cx| editor.is_dirty(cx)));
10535    let save = editor
10536        .update_in(cx, |editor, window, cx| {
10537            editor.save(
10538                SaveOptions {
10539                    format: true,
10540                    autosave: false,
10541                },
10542                project.clone(),
10543                window,
10544                cx,
10545            )
10546        })
10547        .unwrap();
10548    fake_server
10549        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10550            assert_eq!(
10551                params.text_document.uri,
10552                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10553            );
10554            assert_eq!(params.options.tab_size, 8);
10555            Ok(Some(Vec::new()))
10556        })
10557        .next()
10558        .await;
10559    save.await;
10560}
10561
10562#[gpui::test]
10563async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10564    init_test(cx, |settings| {
10565        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10566            Formatter::LanguageServer { name: None },
10567        )))
10568    });
10569
10570    let fs = FakeFs::new(cx.executor());
10571    fs.insert_file(path!("/file.rs"), Default::default()).await;
10572
10573    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10574
10575    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10576    language_registry.add(Arc::new(Language::new(
10577        LanguageConfig {
10578            name: "Rust".into(),
10579            matcher: LanguageMatcher {
10580                path_suffixes: vec!["rs".to_string()],
10581                ..Default::default()
10582            },
10583            ..LanguageConfig::default()
10584        },
10585        Some(tree_sitter_rust::LANGUAGE.into()),
10586    )));
10587    update_test_language_settings(cx, |settings| {
10588        // Enable Prettier formatting for the same buffer, and ensure
10589        // LSP is called instead of Prettier.
10590        settings.defaults.prettier = Some(PrettierSettings {
10591            allowed: true,
10592            ..PrettierSettings::default()
10593        });
10594    });
10595    let mut fake_servers = language_registry.register_fake_lsp(
10596        "Rust",
10597        FakeLspAdapter {
10598            capabilities: lsp::ServerCapabilities {
10599                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10600                ..Default::default()
10601            },
10602            ..Default::default()
10603        },
10604    );
10605
10606    let buffer = project
10607        .update(cx, |project, cx| {
10608            project.open_local_buffer(path!("/file.rs"), cx)
10609        })
10610        .await
10611        .unwrap();
10612
10613    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10614    let (editor, cx) = cx.add_window_view(|window, cx| {
10615        build_editor_with_project(project.clone(), buffer, window, cx)
10616    });
10617    editor.update_in(cx, |editor, window, cx| {
10618        editor.set_text("one\ntwo\nthree\n", window, cx)
10619    });
10620
10621    cx.executor().start_waiting();
10622    let fake_server = fake_servers.next().await.unwrap();
10623
10624    let format = editor
10625        .update_in(cx, |editor, window, cx| {
10626            editor.perform_format(
10627                project.clone(),
10628                FormatTrigger::Manual,
10629                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10630                window,
10631                cx,
10632            )
10633        })
10634        .unwrap();
10635    fake_server
10636        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10637            assert_eq!(
10638                params.text_document.uri,
10639                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10640            );
10641            assert_eq!(params.options.tab_size, 4);
10642            Ok(Some(vec![lsp::TextEdit::new(
10643                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10644                ", ".to_string(),
10645            )]))
10646        })
10647        .next()
10648        .await;
10649    cx.executor().start_waiting();
10650    format.await;
10651    assert_eq!(
10652        editor.update(cx, |editor, cx| editor.text(cx)),
10653        "one, two\nthree\n"
10654    );
10655
10656    editor.update_in(cx, |editor, window, cx| {
10657        editor.set_text("one\ntwo\nthree\n", window, cx)
10658    });
10659    // Ensure we don't lock if formatting hangs.
10660    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10661        move |params, _| async move {
10662            assert_eq!(
10663                params.text_document.uri,
10664                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
10665            );
10666            futures::future::pending::<()>().await;
10667            unreachable!()
10668        },
10669    );
10670    let format = editor
10671        .update_in(cx, |editor, window, cx| {
10672            editor.perform_format(
10673                project,
10674                FormatTrigger::Manual,
10675                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10676                window,
10677                cx,
10678            )
10679        })
10680        .unwrap();
10681    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10682    cx.executor().start_waiting();
10683    format.await;
10684    assert_eq!(
10685        editor.update(cx, |editor, cx| editor.text(cx)),
10686        "one\ntwo\nthree\n"
10687    );
10688}
10689
10690#[gpui::test]
10691async fn test_multiple_formatters(cx: &mut TestAppContext) {
10692    init_test(cx, |settings| {
10693        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10694        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10695            Formatter::LanguageServer { name: None },
10696            Formatter::CodeActions(
10697                [
10698                    ("code-action-1".into(), true),
10699                    ("code-action-2".into(), true),
10700                ]
10701                .into_iter()
10702                .collect(),
10703            ),
10704        ])))
10705    });
10706
10707    let fs = FakeFs::new(cx.executor());
10708    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10709        .await;
10710
10711    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10712    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10713    language_registry.add(rust_lang());
10714
10715    let mut fake_servers = language_registry.register_fake_lsp(
10716        "Rust",
10717        FakeLspAdapter {
10718            capabilities: lsp::ServerCapabilities {
10719                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10720                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10721                    commands: vec!["the-command-for-code-action-1".into()],
10722                    ..Default::default()
10723                }),
10724                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10725                ..Default::default()
10726            },
10727            ..Default::default()
10728        },
10729    );
10730
10731    let buffer = project
10732        .update(cx, |project, cx| {
10733            project.open_local_buffer(path!("/file.rs"), cx)
10734        })
10735        .await
10736        .unwrap();
10737
10738    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10739    let (editor, cx) = cx.add_window_view(|window, cx| {
10740        build_editor_with_project(project.clone(), buffer, window, cx)
10741    });
10742
10743    cx.executor().start_waiting();
10744
10745    let fake_server = fake_servers.next().await.unwrap();
10746    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10747        move |_params, _| async move {
10748            Ok(Some(vec![lsp::TextEdit::new(
10749                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10750                "applied-formatting\n".to_string(),
10751            )]))
10752        },
10753    );
10754    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10755        move |params, _| async move {
10756            assert_eq!(
10757                params.context.only,
10758                Some(vec!["code-action-1".into(), "code-action-2".into()])
10759            );
10760            let uri = lsp::Url::from_file_path(path!("/file.rs")).unwrap();
10761            Ok(Some(vec![
10762                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10763                    kind: Some("code-action-1".into()),
10764                    edit: Some(lsp::WorkspaceEdit::new(
10765                        [(
10766                            uri.clone(),
10767                            vec![lsp::TextEdit::new(
10768                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10769                                "applied-code-action-1-edit\n".to_string(),
10770                            )],
10771                        )]
10772                        .into_iter()
10773                        .collect(),
10774                    )),
10775                    command: Some(lsp::Command {
10776                        command: "the-command-for-code-action-1".into(),
10777                        ..Default::default()
10778                    }),
10779                    ..Default::default()
10780                }),
10781                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10782                    kind: Some("code-action-2".into()),
10783                    edit: Some(lsp::WorkspaceEdit::new(
10784                        [(
10785                            uri,
10786                            vec![lsp::TextEdit::new(
10787                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10788                                "applied-code-action-2-edit\n".to_string(),
10789                            )],
10790                        )]
10791                        .into_iter()
10792                        .collect(),
10793                    )),
10794                    ..Default::default()
10795                }),
10796            ]))
10797        },
10798    );
10799
10800    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10801        move |params, _| async move { Ok(params) }
10802    });
10803
10804    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10805    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10806        let fake = fake_server.clone();
10807        let lock = command_lock.clone();
10808        move |params, _| {
10809            assert_eq!(params.command, "the-command-for-code-action-1");
10810            let fake = fake.clone();
10811            let lock = lock.clone();
10812            async move {
10813                lock.lock().await;
10814                fake.server
10815                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10816                        label: None,
10817                        edit: lsp::WorkspaceEdit {
10818                            changes: Some(
10819                                [(
10820                                    lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
10821                                    vec![lsp::TextEdit {
10822                                        range: lsp::Range::new(
10823                                            lsp::Position::new(0, 0),
10824                                            lsp::Position::new(0, 0),
10825                                        ),
10826                                        new_text: "applied-code-action-1-command\n".into(),
10827                                    }],
10828                                )]
10829                                .into_iter()
10830                                .collect(),
10831                            ),
10832                            ..Default::default()
10833                        },
10834                    })
10835                    .await
10836                    .into_response()
10837                    .unwrap();
10838                Ok(Some(json!(null)))
10839            }
10840        }
10841    });
10842
10843    cx.executor().start_waiting();
10844    editor
10845        .update_in(cx, |editor, window, cx| {
10846            editor.perform_format(
10847                project.clone(),
10848                FormatTrigger::Manual,
10849                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10850                window,
10851                cx,
10852            )
10853        })
10854        .unwrap()
10855        .await;
10856    editor.update(cx, |editor, cx| {
10857        assert_eq!(
10858            editor.text(cx),
10859            r#"
10860                applied-code-action-2-edit
10861                applied-code-action-1-command
10862                applied-code-action-1-edit
10863                applied-formatting
10864                one
10865                two
10866                three
10867            "#
10868            .unindent()
10869        );
10870    });
10871
10872    editor.update_in(cx, |editor, window, cx| {
10873        editor.undo(&Default::default(), window, cx);
10874        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10875    });
10876
10877    // Perform a manual edit while waiting for an LSP command
10878    // that's being run as part of a formatting code action.
10879    let lock_guard = command_lock.lock().await;
10880    let format = editor
10881        .update_in(cx, |editor, window, cx| {
10882            editor.perform_format(
10883                project.clone(),
10884                FormatTrigger::Manual,
10885                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10886                window,
10887                cx,
10888            )
10889        })
10890        .unwrap();
10891    cx.run_until_parked();
10892    editor.update(cx, |editor, cx| {
10893        assert_eq!(
10894            editor.text(cx),
10895            r#"
10896                applied-code-action-1-edit
10897                applied-formatting
10898                one
10899                two
10900                three
10901            "#
10902            .unindent()
10903        );
10904
10905        editor.buffer.update(cx, |buffer, cx| {
10906            let ix = buffer.len(cx);
10907            buffer.edit([(ix..ix, "edited\n")], None, cx);
10908        });
10909    });
10910
10911    // Allow the LSP command to proceed. Because the buffer was edited,
10912    // the second code action will not be run.
10913    drop(lock_guard);
10914    format.await;
10915    editor.update_in(cx, |editor, window, cx| {
10916        assert_eq!(
10917            editor.text(cx),
10918            r#"
10919                applied-code-action-1-command
10920                applied-code-action-1-edit
10921                applied-formatting
10922                one
10923                two
10924                three
10925                edited
10926            "#
10927            .unindent()
10928        );
10929
10930        // The manual edit is undone first, because it is the last thing the user did
10931        // (even though the command completed afterwards).
10932        editor.undo(&Default::default(), window, cx);
10933        assert_eq!(
10934            editor.text(cx),
10935            r#"
10936                applied-code-action-1-command
10937                applied-code-action-1-edit
10938                applied-formatting
10939                one
10940                two
10941                three
10942            "#
10943            .unindent()
10944        );
10945
10946        // All the formatting (including the command, which completed after the manual edit)
10947        // is undone together.
10948        editor.undo(&Default::default(), window, cx);
10949        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
10950    });
10951}
10952
10953#[gpui::test]
10954async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
10955    init_test(cx, |settings| {
10956        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10957            Formatter::LanguageServer { name: None },
10958        ])))
10959    });
10960
10961    let fs = FakeFs::new(cx.executor());
10962    fs.insert_file(path!("/file.ts"), Default::default()).await;
10963
10964    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10965
10966    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10967    language_registry.add(Arc::new(Language::new(
10968        LanguageConfig {
10969            name: "TypeScript".into(),
10970            matcher: LanguageMatcher {
10971                path_suffixes: vec!["ts".to_string()],
10972                ..Default::default()
10973            },
10974            ..LanguageConfig::default()
10975        },
10976        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
10977    )));
10978    update_test_language_settings(cx, |settings| {
10979        settings.defaults.prettier = Some(PrettierSettings {
10980            allowed: true,
10981            ..PrettierSettings::default()
10982        });
10983    });
10984    let mut fake_servers = language_registry.register_fake_lsp(
10985        "TypeScript",
10986        FakeLspAdapter {
10987            capabilities: lsp::ServerCapabilities {
10988                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10989                ..Default::default()
10990            },
10991            ..Default::default()
10992        },
10993    );
10994
10995    let buffer = project
10996        .update(cx, |project, cx| {
10997            project.open_local_buffer(path!("/file.ts"), cx)
10998        })
10999        .await
11000        .unwrap();
11001
11002    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11003    let (editor, cx) = cx.add_window_view(|window, cx| {
11004        build_editor_with_project(project.clone(), buffer, window, cx)
11005    });
11006    editor.update_in(cx, |editor, window, cx| {
11007        editor.set_text(
11008            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11009            window,
11010            cx,
11011        )
11012    });
11013
11014    cx.executor().start_waiting();
11015    let fake_server = fake_servers.next().await.unwrap();
11016
11017    let format = editor
11018        .update_in(cx, |editor, window, cx| {
11019            editor.perform_code_action_kind(
11020                project.clone(),
11021                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11022                window,
11023                cx,
11024            )
11025        })
11026        .unwrap();
11027    fake_server
11028        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11029            assert_eq!(
11030                params.text_document.uri,
11031                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11032            );
11033            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11034                lsp::CodeAction {
11035                    title: "Organize Imports".to_string(),
11036                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11037                    edit: Some(lsp::WorkspaceEdit {
11038                        changes: Some(
11039                            [(
11040                                params.text_document.uri.clone(),
11041                                vec![lsp::TextEdit::new(
11042                                    lsp::Range::new(
11043                                        lsp::Position::new(1, 0),
11044                                        lsp::Position::new(2, 0),
11045                                    ),
11046                                    "".to_string(),
11047                                )],
11048                            )]
11049                            .into_iter()
11050                            .collect(),
11051                        ),
11052                        ..Default::default()
11053                    }),
11054                    ..Default::default()
11055                },
11056            )]))
11057        })
11058        .next()
11059        .await;
11060    cx.executor().start_waiting();
11061    format.await;
11062    assert_eq!(
11063        editor.update(cx, |editor, cx| editor.text(cx)),
11064        "import { a } from 'module';\n\nconst x = a;\n"
11065    );
11066
11067    editor.update_in(cx, |editor, window, cx| {
11068        editor.set_text(
11069            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11070            window,
11071            cx,
11072        )
11073    });
11074    // Ensure we don't lock if code action hangs.
11075    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11076        move |params, _| async move {
11077            assert_eq!(
11078                params.text_document.uri,
11079                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
11080            );
11081            futures::future::pending::<()>().await;
11082            unreachable!()
11083        },
11084    );
11085    let format = editor
11086        .update_in(cx, |editor, window, cx| {
11087            editor.perform_code_action_kind(
11088                project,
11089                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11090                window,
11091                cx,
11092            )
11093        })
11094        .unwrap();
11095    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11096    cx.executor().start_waiting();
11097    format.await;
11098    assert_eq!(
11099        editor.update(cx, |editor, cx| editor.text(cx)),
11100        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11101    );
11102}
11103
11104#[gpui::test]
11105async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11106    init_test(cx, |_| {});
11107
11108    let mut cx = EditorLspTestContext::new_rust(
11109        lsp::ServerCapabilities {
11110            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11111            ..Default::default()
11112        },
11113        cx,
11114    )
11115    .await;
11116
11117    cx.set_state(indoc! {"
11118        one.twoˇ
11119    "});
11120
11121    // The format request takes a long time. When it completes, it inserts
11122    // a newline and an indent before the `.`
11123    cx.lsp
11124        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11125            let executor = cx.background_executor().clone();
11126            async move {
11127                executor.timer(Duration::from_millis(100)).await;
11128                Ok(Some(vec![lsp::TextEdit {
11129                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11130                    new_text: "\n    ".into(),
11131                }]))
11132            }
11133        });
11134
11135    // Submit a format request.
11136    let format_1 = cx
11137        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11138        .unwrap();
11139    cx.executor().run_until_parked();
11140
11141    // Submit a second format request.
11142    let format_2 = cx
11143        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11144        .unwrap();
11145    cx.executor().run_until_parked();
11146
11147    // Wait for both format requests to complete
11148    cx.executor().advance_clock(Duration::from_millis(200));
11149    cx.executor().start_waiting();
11150    format_1.await.unwrap();
11151    cx.executor().start_waiting();
11152    format_2.await.unwrap();
11153
11154    // The formatting edits only happens once.
11155    cx.assert_editor_state(indoc! {"
11156        one
11157            .twoˇ
11158    "});
11159}
11160
11161#[gpui::test]
11162async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
11163    init_test(cx, |settings| {
11164        settings.defaults.formatter = Some(SelectedFormatter::Auto)
11165    });
11166
11167    let mut cx = EditorLspTestContext::new_rust(
11168        lsp::ServerCapabilities {
11169            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11170            ..Default::default()
11171        },
11172        cx,
11173    )
11174    .await;
11175
11176    // Set up a buffer white some trailing whitespace and no trailing newline.
11177    cx.set_state(
11178        &[
11179            "one ",   //
11180            "twoˇ",   //
11181            "three ", //
11182            "four",   //
11183        ]
11184        .join("\n"),
11185    );
11186
11187    // Submit a format request.
11188    let format = cx
11189        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11190        .unwrap();
11191
11192    // Record which buffer changes have been sent to the language server
11193    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
11194    cx.lsp
11195        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
11196            let buffer_changes = buffer_changes.clone();
11197            move |params, _| {
11198                buffer_changes.lock().extend(
11199                    params
11200                        .content_changes
11201                        .into_iter()
11202                        .map(|e| (e.range.unwrap(), e.text)),
11203                );
11204            }
11205        });
11206
11207    // Handle formatting requests to the language server.
11208    cx.lsp
11209        .set_request_handler::<lsp::request::Formatting, _, _>({
11210            let buffer_changes = buffer_changes.clone();
11211            move |_, _| {
11212                // When formatting is requested, trailing whitespace has already been stripped,
11213                // and the trailing newline has already been added.
11214                assert_eq!(
11215                    &buffer_changes.lock()[1..],
11216                    &[
11217                        (
11218                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11219                            "".into()
11220                        ),
11221                        (
11222                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11223                            "".into()
11224                        ),
11225                        (
11226                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11227                            "\n".into()
11228                        ),
11229                    ]
11230                );
11231
11232                // Insert blank lines between each line of the buffer.
11233                async move {
11234                    Ok(Some(vec![
11235                        lsp::TextEdit {
11236                            range: lsp::Range::new(
11237                                lsp::Position::new(1, 0),
11238                                lsp::Position::new(1, 0),
11239                            ),
11240                            new_text: "\n".into(),
11241                        },
11242                        lsp::TextEdit {
11243                            range: lsp::Range::new(
11244                                lsp::Position::new(2, 0),
11245                                lsp::Position::new(2, 0),
11246                            ),
11247                            new_text: "\n".into(),
11248                        },
11249                    ]))
11250                }
11251            }
11252        });
11253
11254    // After formatting the buffer, the trailing whitespace is stripped,
11255    // a newline is appended, and the edits provided by the language server
11256    // have been applied.
11257    format.await.unwrap();
11258    cx.assert_editor_state(
11259        &[
11260            "one",   //
11261            "",      //
11262            "twoˇ",  //
11263            "",      //
11264            "three", //
11265            "four",  //
11266            "",      //
11267        ]
11268        .join("\n"),
11269    );
11270
11271    // Undoing the formatting undoes the trailing whitespace removal, the
11272    // trailing newline, and the LSP edits.
11273    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11274    cx.assert_editor_state(
11275        &[
11276            "one ",   //
11277            "twoˇ",   //
11278            "three ", //
11279            "four",   //
11280        ]
11281        .join("\n"),
11282    );
11283}
11284
11285#[gpui::test]
11286async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11287    cx: &mut TestAppContext,
11288) {
11289    init_test(cx, |_| {});
11290
11291    cx.update(|cx| {
11292        cx.update_global::<SettingsStore, _>(|settings, cx| {
11293            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11294                settings.auto_signature_help = Some(true);
11295            });
11296        });
11297    });
11298
11299    let mut cx = EditorLspTestContext::new_rust(
11300        lsp::ServerCapabilities {
11301            signature_help_provider: Some(lsp::SignatureHelpOptions {
11302                ..Default::default()
11303            }),
11304            ..Default::default()
11305        },
11306        cx,
11307    )
11308    .await;
11309
11310    let language = Language::new(
11311        LanguageConfig {
11312            name: "Rust".into(),
11313            brackets: BracketPairConfig {
11314                pairs: vec![
11315                    BracketPair {
11316                        start: "{".to_string(),
11317                        end: "}".to_string(),
11318                        close: true,
11319                        surround: true,
11320                        newline: true,
11321                    },
11322                    BracketPair {
11323                        start: "(".to_string(),
11324                        end: ")".to_string(),
11325                        close: true,
11326                        surround: true,
11327                        newline: true,
11328                    },
11329                    BracketPair {
11330                        start: "/*".to_string(),
11331                        end: " */".to_string(),
11332                        close: true,
11333                        surround: true,
11334                        newline: true,
11335                    },
11336                    BracketPair {
11337                        start: "[".to_string(),
11338                        end: "]".to_string(),
11339                        close: false,
11340                        surround: false,
11341                        newline: true,
11342                    },
11343                    BracketPair {
11344                        start: "\"".to_string(),
11345                        end: "\"".to_string(),
11346                        close: true,
11347                        surround: true,
11348                        newline: false,
11349                    },
11350                    BracketPair {
11351                        start: "<".to_string(),
11352                        end: ">".to_string(),
11353                        close: false,
11354                        surround: true,
11355                        newline: true,
11356                    },
11357                ],
11358                ..Default::default()
11359            },
11360            autoclose_before: "})]".to_string(),
11361            ..Default::default()
11362        },
11363        Some(tree_sitter_rust::LANGUAGE.into()),
11364    );
11365    let language = Arc::new(language);
11366
11367    cx.language_registry().add(language.clone());
11368    cx.update_buffer(|buffer, cx| {
11369        buffer.set_language(Some(language), cx);
11370    });
11371
11372    cx.set_state(
11373        &r#"
11374            fn main() {
11375                sampleˇ
11376            }
11377        "#
11378        .unindent(),
11379    );
11380
11381    cx.update_editor(|editor, window, cx| {
11382        editor.handle_input("(", window, cx);
11383    });
11384    cx.assert_editor_state(
11385        &"
11386            fn main() {
11387                sample(ˇ)
11388            }
11389        "
11390        .unindent(),
11391    );
11392
11393    let mocked_response = lsp::SignatureHelp {
11394        signatures: vec![lsp::SignatureInformation {
11395            label: "fn sample(param1: u8, param2: u8)".to_string(),
11396            documentation: None,
11397            parameters: Some(vec![
11398                lsp::ParameterInformation {
11399                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11400                    documentation: None,
11401                },
11402                lsp::ParameterInformation {
11403                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11404                    documentation: None,
11405                },
11406            ]),
11407            active_parameter: None,
11408        }],
11409        active_signature: Some(0),
11410        active_parameter: Some(0),
11411    };
11412    handle_signature_help_request(&mut cx, mocked_response).await;
11413
11414    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11415        .await;
11416
11417    cx.editor(|editor, _, _| {
11418        let signature_help_state = editor.signature_help_state.popover().cloned();
11419        let signature = signature_help_state.unwrap();
11420        assert_eq!(
11421            signature.signatures[signature.current_signature].label,
11422            "fn sample(param1: u8, param2: u8)"
11423        );
11424    });
11425}
11426
11427#[gpui::test]
11428async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11429    init_test(cx, |_| {});
11430
11431    cx.update(|cx| {
11432        cx.update_global::<SettingsStore, _>(|settings, cx| {
11433            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11434                settings.auto_signature_help = Some(false);
11435                settings.show_signature_help_after_edits = Some(false);
11436            });
11437        });
11438    });
11439
11440    let mut cx = EditorLspTestContext::new_rust(
11441        lsp::ServerCapabilities {
11442            signature_help_provider: Some(lsp::SignatureHelpOptions {
11443                ..Default::default()
11444            }),
11445            ..Default::default()
11446        },
11447        cx,
11448    )
11449    .await;
11450
11451    let language = Language::new(
11452        LanguageConfig {
11453            name: "Rust".into(),
11454            brackets: BracketPairConfig {
11455                pairs: vec![
11456                    BracketPair {
11457                        start: "{".to_string(),
11458                        end: "}".to_string(),
11459                        close: true,
11460                        surround: true,
11461                        newline: true,
11462                    },
11463                    BracketPair {
11464                        start: "(".to_string(),
11465                        end: ")".to_string(),
11466                        close: true,
11467                        surround: true,
11468                        newline: true,
11469                    },
11470                    BracketPair {
11471                        start: "/*".to_string(),
11472                        end: " */".to_string(),
11473                        close: true,
11474                        surround: true,
11475                        newline: true,
11476                    },
11477                    BracketPair {
11478                        start: "[".to_string(),
11479                        end: "]".to_string(),
11480                        close: false,
11481                        surround: false,
11482                        newline: true,
11483                    },
11484                    BracketPair {
11485                        start: "\"".to_string(),
11486                        end: "\"".to_string(),
11487                        close: true,
11488                        surround: true,
11489                        newline: false,
11490                    },
11491                    BracketPair {
11492                        start: "<".to_string(),
11493                        end: ">".to_string(),
11494                        close: false,
11495                        surround: true,
11496                        newline: true,
11497                    },
11498                ],
11499                ..Default::default()
11500            },
11501            autoclose_before: "})]".to_string(),
11502            ..Default::default()
11503        },
11504        Some(tree_sitter_rust::LANGUAGE.into()),
11505    );
11506    let language = Arc::new(language);
11507
11508    cx.language_registry().add(language.clone());
11509    cx.update_buffer(|buffer, cx| {
11510        buffer.set_language(Some(language), cx);
11511    });
11512
11513    // Ensure that signature_help is not called when no signature help is enabled.
11514    cx.set_state(
11515        &r#"
11516            fn main() {
11517                sampleˇ
11518            }
11519        "#
11520        .unindent(),
11521    );
11522    cx.update_editor(|editor, window, cx| {
11523        editor.handle_input("(", window, cx);
11524    });
11525    cx.assert_editor_state(
11526        &"
11527            fn main() {
11528                sample(ˇ)
11529            }
11530        "
11531        .unindent(),
11532    );
11533    cx.editor(|editor, _, _| {
11534        assert!(editor.signature_help_state.task().is_none());
11535    });
11536
11537    let mocked_response = lsp::SignatureHelp {
11538        signatures: vec![lsp::SignatureInformation {
11539            label: "fn sample(param1: u8, param2: u8)".to_string(),
11540            documentation: None,
11541            parameters: Some(vec![
11542                lsp::ParameterInformation {
11543                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11544                    documentation: None,
11545                },
11546                lsp::ParameterInformation {
11547                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11548                    documentation: None,
11549                },
11550            ]),
11551            active_parameter: None,
11552        }],
11553        active_signature: Some(0),
11554        active_parameter: Some(0),
11555    };
11556
11557    // Ensure that signature_help is called when enabled afte edits
11558    cx.update(|_, cx| {
11559        cx.update_global::<SettingsStore, _>(|settings, cx| {
11560            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11561                settings.auto_signature_help = Some(false);
11562                settings.show_signature_help_after_edits = Some(true);
11563            });
11564        });
11565    });
11566    cx.set_state(
11567        &r#"
11568            fn main() {
11569                sampleˇ
11570            }
11571        "#
11572        .unindent(),
11573    );
11574    cx.update_editor(|editor, window, cx| {
11575        editor.handle_input("(", window, cx);
11576    });
11577    cx.assert_editor_state(
11578        &"
11579            fn main() {
11580                sample(ˇ)
11581            }
11582        "
11583        .unindent(),
11584    );
11585    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11586    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11587        .await;
11588    cx.update_editor(|editor, _, _| {
11589        let signature_help_state = editor.signature_help_state.popover().cloned();
11590        assert!(signature_help_state.is_some());
11591        let signature = signature_help_state.unwrap();
11592        assert_eq!(
11593            signature.signatures[signature.current_signature].label,
11594            "fn sample(param1: u8, param2: u8)"
11595        );
11596        editor.signature_help_state = SignatureHelpState::default();
11597    });
11598
11599    // Ensure that signature_help is called when auto signature help override is enabled
11600    cx.update(|_, cx| {
11601        cx.update_global::<SettingsStore, _>(|settings, cx| {
11602            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11603                settings.auto_signature_help = Some(true);
11604                settings.show_signature_help_after_edits = Some(false);
11605            });
11606        });
11607    });
11608    cx.set_state(
11609        &r#"
11610            fn main() {
11611                sampleˇ
11612            }
11613        "#
11614        .unindent(),
11615    );
11616    cx.update_editor(|editor, window, cx| {
11617        editor.handle_input("(", window, cx);
11618    });
11619    cx.assert_editor_state(
11620        &"
11621            fn main() {
11622                sample(ˇ)
11623            }
11624        "
11625        .unindent(),
11626    );
11627    handle_signature_help_request(&mut cx, mocked_response).await;
11628    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11629        .await;
11630    cx.editor(|editor, _, _| {
11631        let signature_help_state = editor.signature_help_state.popover().cloned();
11632        assert!(signature_help_state.is_some());
11633        let signature = signature_help_state.unwrap();
11634        assert_eq!(
11635            signature.signatures[signature.current_signature].label,
11636            "fn sample(param1: u8, param2: u8)"
11637        );
11638    });
11639}
11640
11641#[gpui::test]
11642async fn test_signature_help(cx: &mut TestAppContext) {
11643    init_test(cx, |_| {});
11644    cx.update(|cx| {
11645        cx.update_global::<SettingsStore, _>(|settings, cx| {
11646            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11647                settings.auto_signature_help = Some(true);
11648            });
11649        });
11650    });
11651
11652    let mut cx = EditorLspTestContext::new_rust(
11653        lsp::ServerCapabilities {
11654            signature_help_provider: Some(lsp::SignatureHelpOptions {
11655                ..Default::default()
11656            }),
11657            ..Default::default()
11658        },
11659        cx,
11660    )
11661    .await;
11662
11663    // A test that directly calls `show_signature_help`
11664    cx.update_editor(|editor, window, cx| {
11665        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11666    });
11667
11668    let mocked_response = lsp::SignatureHelp {
11669        signatures: vec![lsp::SignatureInformation {
11670            label: "fn sample(param1: u8, param2: u8)".to_string(),
11671            documentation: None,
11672            parameters: Some(vec![
11673                lsp::ParameterInformation {
11674                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11675                    documentation: None,
11676                },
11677                lsp::ParameterInformation {
11678                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11679                    documentation: None,
11680                },
11681            ]),
11682            active_parameter: None,
11683        }],
11684        active_signature: Some(0),
11685        active_parameter: Some(0),
11686    };
11687    handle_signature_help_request(&mut cx, mocked_response).await;
11688
11689    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11690        .await;
11691
11692    cx.editor(|editor, _, _| {
11693        let signature_help_state = editor.signature_help_state.popover().cloned();
11694        assert!(signature_help_state.is_some());
11695        let signature = signature_help_state.unwrap();
11696        assert_eq!(
11697            signature.signatures[signature.current_signature].label,
11698            "fn sample(param1: u8, param2: u8)"
11699        );
11700    });
11701
11702    // When exiting outside from inside the brackets, `signature_help` is closed.
11703    cx.set_state(indoc! {"
11704        fn main() {
11705            sample(ˇ);
11706        }
11707
11708        fn sample(param1: u8, param2: u8) {}
11709    "});
11710
11711    cx.update_editor(|editor, window, cx| {
11712        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11713            s.select_ranges([0..0])
11714        });
11715    });
11716
11717    let mocked_response = lsp::SignatureHelp {
11718        signatures: Vec::new(),
11719        active_signature: None,
11720        active_parameter: None,
11721    };
11722    handle_signature_help_request(&mut cx, mocked_response).await;
11723
11724    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11725        .await;
11726
11727    cx.editor(|editor, _, _| {
11728        assert!(!editor.signature_help_state.is_shown());
11729    });
11730
11731    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11732    cx.set_state(indoc! {"
11733        fn main() {
11734            sample(ˇ);
11735        }
11736
11737        fn sample(param1: u8, param2: u8) {}
11738    "});
11739
11740    let mocked_response = lsp::SignatureHelp {
11741        signatures: vec![lsp::SignatureInformation {
11742            label: "fn sample(param1: u8, param2: u8)".to_string(),
11743            documentation: None,
11744            parameters: Some(vec![
11745                lsp::ParameterInformation {
11746                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11747                    documentation: None,
11748                },
11749                lsp::ParameterInformation {
11750                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11751                    documentation: None,
11752                },
11753            ]),
11754            active_parameter: None,
11755        }],
11756        active_signature: Some(0),
11757        active_parameter: Some(0),
11758    };
11759    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11760    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11761        .await;
11762    cx.editor(|editor, _, _| {
11763        assert!(editor.signature_help_state.is_shown());
11764    });
11765
11766    // Restore the popover with more parameter input
11767    cx.set_state(indoc! {"
11768        fn main() {
11769            sample(param1, param2ˇ);
11770        }
11771
11772        fn sample(param1: u8, param2: u8) {}
11773    "});
11774
11775    let mocked_response = lsp::SignatureHelp {
11776        signatures: vec![lsp::SignatureInformation {
11777            label: "fn sample(param1: u8, param2: u8)".to_string(),
11778            documentation: None,
11779            parameters: Some(vec![
11780                lsp::ParameterInformation {
11781                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11782                    documentation: None,
11783                },
11784                lsp::ParameterInformation {
11785                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11786                    documentation: None,
11787                },
11788            ]),
11789            active_parameter: None,
11790        }],
11791        active_signature: Some(0),
11792        active_parameter: Some(1),
11793    };
11794    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11795    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11796        .await;
11797
11798    // When selecting a range, the popover is gone.
11799    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11800    cx.update_editor(|editor, window, cx| {
11801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11802            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11803        })
11804    });
11805    cx.assert_editor_state(indoc! {"
11806        fn main() {
11807            sample(param1, «ˇparam2»);
11808        }
11809
11810        fn sample(param1: u8, param2: u8) {}
11811    "});
11812    cx.editor(|editor, _, _| {
11813        assert!(!editor.signature_help_state.is_shown());
11814    });
11815
11816    // When unselecting again, the popover is back if within the brackets.
11817    cx.update_editor(|editor, window, cx| {
11818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11819            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11820        })
11821    });
11822    cx.assert_editor_state(indoc! {"
11823        fn main() {
11824            sample(param1, ˇparam2);
11825        }
11826
11827        fn sample(param1: u8, param2: u8) {}
11828    "});
11829    handle_signature_help_request(&mut cx, mocked_response).await;
11830    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11831        .await;
11832    cx.editor(|editor, _, _| {
11833        assert!(editor.signature_help_state.is_shown());
11834    });
11835
11836    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11837    cx.update_editor(|editor, window, cx| {
11838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11839            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11840            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11841        })
11842    });
11843    cx.assert_editor_state(indoc! {"
11844        fn main() {
11845            sample(param1, ˇparam2);
11846        }
11847
11848        fn sample(param1: u8, param2: u8) {}
11849    "});
11850
11851    let mocked_response = lsp::SignatureHelp {
11852        signatures: vec![lsp::SignatureInformation {
11853            label: "fn sample(param1: u8, param2: u8)".to_string(),
11854            documentation: None,
11855            parameters: Some(vec![
11856                lsp::ParameterInformation {
11857                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11858                    documentation: None,
11859                },
11860                lsp::ParameterInformation {
11861                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11862                    documentation: None,
11863                },
11864            ]),
11865            active_parameter: None,
11866        }],
11867        active_signature: Some(0),
11868        active_parameter: Some(1),
11869    };
11870    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11871    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11872        .await;
11873    cx.update_editor(|editor, _, cx| {
11874        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
11875    });
11876    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11877        .await;
11878    cx.update_editor(|editor, window, cx| {
11879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11880            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11881        })
11882    });
11883    cx.assert_editor_state(indoc! {"
11884        fn main() {
11885            sample(param1, «ˇparam2»);
11886        }
11887
11888        fn sample(param1: u8, param2: u8) {}
11889    "});
11890    cx.update_editor(|editor, window, cx| {
11891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11892            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11893        })
11894    });
11895    cx.assert_editor_state(indoc! {"
11896        fn main() {
11897            sample(param1, ˇparam2);
11898        }
11899
11900        fn sample(param1: u8, param2: u8) {}
11901    "});
11902    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
11903        .await;
11904}
11905
11906#[gpui::test]
11907async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
11908    init_test(cx, |_| {});
11909
11910    let mut cx = EditorLspTestContext::new_rust(
11911        lsp::ServerCapabilities {
11912            signature_help_provider: Some(lsp::SignatureHelpOptions {
11913                ..Default::default()
11914            }),
11915            ..Default::default()
11916        },
11917        cx,
11918    )
11919    .await;
11920
11921    cx.set_state(indoc! {"
11922        fn main() {
11923            overloadedˇ
11924        }
11925    "});
11926
11927    cx.update_editor(|editor, window, cx| {
11928        editor.handle_input("(", window, cx);
11929        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11930    });
11931
11932    // Mock response with 3 signatures
11933    let mocked_response = lsp::SignatureHelp {
11934        signatures: vec![
11935            lsp::SignatureInformation {
11936                label: "fn overloaded(x: i32)".to_string(),
11937                documentation: None,
11938                parameters: Some(vec![lsp::ParameterInformation {
11939                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11940                    documentation: None,
11941                }]),
11942                active_parameter: None,
11943            },
11944            lsp::SignatureInformation {
11945                label: "fn overloaded(x: i32, y: i32)".to_string(),
11946                documentation: None,
11947                parameters: Some(vec![
11948                    lsp::ParameterInformation {
11949                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11950                        documentation: None,
11951                    },
11952                    lsp::ParameterInformation {
11953                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11954                        documentation: None,
11955                    },
11956                ]),
11957                active_parameter: None,
11958            },
11959            lsp::SignatureInformation {
11960                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
11961                documentation: None,
11962                parameters: Some(vec![
11963                    lsp::ParameterInformation {
11964                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
11965                        documentation: None,
11966                    },
11967                    lsp::ParameterInformation {
11968                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
11969                        documentation: None,
11970                    },
11971                    lsp::ParameterInformation {
11972                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
11973                        documentation: None,
11974                    },
11975                ]),
11976                active_parameter: None,
11977            },
11978        ],
11979        active_signature: Some(1),
11980        active_parameter: Some(0),
11981    };
11982    handle_signature_help_request(&mut cx, mocked_response).await;
11983
11984    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11985        .await;
11986
11987    // Verify we have multiple signatures and the right one is selected
11988    cx.editor(|editor, _, _| {
11989        let popover = editor.signature_help_state.popover().cloned().unwrap();
11990        assert_eq!(popover.signatures.len(), 3);
11991        // active_signature was 1, so that should be the current
11992        assert_eq!(popover.current_signature, 1);
11993        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
11994        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
11995        assert_eq!(
11996            popover.signatures[2].label,
11997            "fn overloaded(x: i32, y: i32, z: i32)"
11998        );
11999    });
12000
12001    // Test navigation functionality
12002    cx.update_editor(|editor, window, cx| {
12003        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12004    });
12005
12006    cx.editor(|editor, _, _| {
12007        let popover = editor.signature_help_state.popover().cloned().unwrap();
12008        assert_eq!(popover.current_signature, 2);
12009    });
12010
12011    // Test wrap around
12012    cx.update_editor(|editor, window, cx| {
12013        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12014    });
12015
12016    cx.editor(|editor, _, _| {
12017        let popover = editor.signature_help_state.popover().cloned().unwrap();
12018        assert_eq!(popover.current_signature, 0);
12019    });
12020
12021    // Test previous navigation
12022    cx.update_editor(|editor, window, cx| {
12023        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12024    });
12025
12026    cx.editor(|editor, _, _| {
12027        let popover = editor.signature_help_state.popover().cloned().unwrap();
12028        assert_eq!(popover.current_signature, 2);
12029    });
12030}
12031
12032#[gpui::test]
12033async fn test_completion_mode(cx: &mut TestAppContext) {
12034    init_test(cx, |_| {});
12035    let mut cx = EditorLspTestContext::new_rust(
12036        lsp::ServerCapabilities {
12037            completion_provider: Some(lsp::CompletionOptions {
12038                resolve_provider: Some(true),
12039                ..Default::default()
12040            }),
12041            ..Default::default()
12042        },
12043        cx,
12044    )
12045    .await;
12046
12047    struct Run {
12048        run_description: &'static str,
12049        initial_state: String,
12050        buffer_marked_text: String,
12051        completion_label: &'static str,
12052        completion_text: &'static str,
12053        expected_with_insert_mode: String,
12054        expected_with_replace_mode: String,
12055        expected_with_replace_subsequence_mode: String,
12056        expected_with_replace_suffix_mode: String,
12057    }
12058
12059    let runs = [
12060        Run {
12061            run_description: "Start of word matches completion text",
12062            initial_state: "before ediˇ after".into(),
12063            buffer_marked_text: "before <edi|> after".into(),
12064            completion_label: "editor",
12065            completion_text: "editor",
12066            expected_with_insert_mode: "before editorˇ after".into(),
12067            expected_with_replace_mode: "before editorˇ after".into(),
12068            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12069            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12070        },
12071        Run {
12072            run_description: "Accept same text at the middle of the word",
12073            initial_state: "before ediˇtor after".into(),
12074            buffer_marked_text: "before <edi|tor> after".into(),
12075            completion_label: "editor",
12076            completion_text: "editor",
12077            expected_with_insert_mode: "before editorˇtor after".into(),
12078            expected_with_replace_mode: "before editorˇ after".into(),
12079            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12080            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12081        },
12082        Run {
12083            run_description: "End of word matches completion text -- cursor at end",
12084            initial_state: "before torˇ after".into(),
12085            buffer_marked_text: "before <tor|> after".into(),
12086            completion_label: "editor",
12087            completion_text: "editor",
12088            expected_with_insert_mode: "before editorˇ after".into(),
12089            expected_with_replace_mode: "before editorˇ after".into(),
12090            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12091            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12092        },
12093        Run {
12094            run_description: "End of word matches completion text -- cursor at start",
12095            initial_state: "before ˇtor after".into(),
12096            buffer_marked_text: "before <|tor> after".into(),
12097            completion_label: "editor",
12098            completion_text: "editor",
12099            expected_with_insert_mode: "before editorˇtor after".into(),
12100            expected_with_replace_mode: "before editorˇ after".into(),
12101            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12102            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12103        },
12104        Run {
12105            run_description: "Prepend text containing whitespace",
12106            initial_state: "pˇfield: bool".into(),
12107            buffer_marked_text: "<p|field>: bool".into(),
12108            completion_label: "pub ",
12109            completion_text: "pub ",
12110            expected_with_insert_mode: "pub ˇfield: bool".into(),
12111            expected_with_replace_mode: "pub ˇ: bool".into(),
12112            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12113            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12114        },
12115        Run {
12116            run_description: "Add element to start of list",
12117            initial_state: "[element_ˇelement_2]".into(),
12118            buffer_marked_text: "[<element_|element_2>]".into(),
12119            completion_label: "element_1",
12120            completion_text: "element_1",
12121            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12122            expected_with_replace_mode: "[element_1ˇ]".into(),
12123            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12124            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12125        },
12126        Run {
12127            run_description: "Add element to start of list -- first and second elements are equal",
12128            initial_state: "[elˇelement]".into(),
12129            buffer_marked_text: "[<el|element>]".into(),
12130            completion_label: "element",
12131            completion_text: "element",
12132            expected_with_insert_mode: "[elementˇelement]".into(),
12133            expected_with_replace_mode: "[elementˇ]".into(),
12134            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
12135            expected_with_replace_suffix_mode: "[elementˇ]".into(),
12136        },
12137        Run {
12138            run_description: "Ends with matching suffix",
12139            initial_state: "SubˇError".into(),
12140            buffer_marked_text: "<Sub|Error>".into(),
12141            completion_label: "SubscriptionError",
12142            completion_text: "SubscriptionError",
12143            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
12144            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12145            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12146            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
12147        },
12148        Run {
12149            run_description: "Suffix is a subsequence -- contiguous",
12150            initial_state: "SubˇErr".into(),
12151            buffer_marked_text: "<Sub|Err>".into(),
12152            completion_label: "SubscriptionError",
12153            completion_text: "SubscriptionError",
12154            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
12155            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12156            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12157            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
12158        },
12159        Run {
12160            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
12161            initial_state: "Suˇscrirr".into(),
12162            buffer_marked_text: "<Su|scrirr>".into(),
12163            completion_label: "SubscriptionError",
12164            completion_text: "SubscriptionError",
12165            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
12166            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12167            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12168            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
12169        },
12170        Run {
12171            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
12172            initial_state: "foo(indˇix)".into(),
12173            buffer_marked_text: "foo(<ind|ix>)".into(),
12174            completion_label: "node_index",
12175            completion_text: "node_index",
12176            expected_with_insert_mode: "foo(node_indexˇix)".into(),
12177            expected_with_replace_mode: "foo(node_indexˇ)".into(),
12178            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
12179            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
12180        },
12181        Run {
12182            run_description: "Replace range ends before cursor - should extend to cursor",
12183            initial_state: "before editˇo after".into(),
12184            buffer_marked_text: "before <{ed}>it|o after".into(),
12185            completion_label: "editor",
12186            completion_text: "editor",
12187            expected_with_insert_mode: "before editorˇo after".into(),
12188            expected_with_replace_mode: "before editorˇo after".into(),
12189            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
12190            expected_with_replace_suffix_mode: "before editorˇo after".into(),
12191        },
12192        Run {
12193            run_description: "Uses label for suffix matching",
12194            initial_state: "before ediˇtor after".into(),
12195            buffer_marked_text: "before <edi|tor> after".into(),
12196            completion_label: "editor",
12197            completion_text: "editor()",
12198            expected_with_insert_mode: "before editor()ˇtor after".into(),
12199            expected_with_replace_mode: "before editor()ˇ after".into(),
12200            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12201            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12202        },
12203        Run {
12204            run_description: "Case insensitive subsequence and suffix matching",
12205            initial_state: "before EDiˇtoR after".into(),
12206            buffer_marked_text: "before <EDi|toR> after".into(),
12207            completion_label: "editor",
12208            completion_text: "editor",
12209            expected_with_insert_mode: "before editorˇtoR after".into(),
12210            expected_with_replace_mode: "before editorˇ after".into(),
12211            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12212            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12213        },
12214    ];
12215
12216    for run in runs {
12217        let run_variations = [
12218            (LspInsertMode::Insert, run.expected_with_insert_mode),
12219            (LspInsertMode::Replace, run.expected_with_replace_mode),
12220            (
12221                LspInsertMode::ReplaceSubsequence,
12222                run.expected_with_replace_subsequence_mode,
12223            ),
12224            (
12225                LspInsertMode::ReplaceSuffix,
12226                run.expected_with_replace_suffix_mode,
12227            ),
12228        ];
12229
12230        for (lsp_insert_mode, expected_text) in run_variations {
12231            eprintln!(
12232                "run = {:?}, mode = {lsp_insert_mode:.?}",
12233                run.run_description,
12234            );
12235
12236            update_test_language_settings(&mut cx, |settings| {
12237                settings.defaults.completions = Some(CompletionSettings {
12238                    lsp_insert_mode,
12239                    words: WordsCompletionMode::Disabled,
12240                    words_min_length: 0,
12241                    lsp: true,
12242                    lsp_fetch_timeout_ms: 0,
12243                });
12244            });
12245
12246            cx.set_state(&run.initial_state);
12247            cx.update_editor(|editor, window, cx| {
12248                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12249            });
12250
12251            let counter = Arc::new(AtomicUsize::new(0));
12252            handle_completion_request_with_insert_and_replace(
12253                &mut cx,
12254                &run.buffer_marked_text,
12255                vec![(run.completion_label, run.completion_text)],
12256                counter.clone(),
12257            )
12258            .await;
12259            cx.condition(|editor, _| editor.context_menu_visible())
12260                .await;
12261            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12262
12263            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12264                editor
12265                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12266                    .unwrap()
12267            });
12268            cx.assert_editor_state(&expected_text);
12269            handle_resolve_completion_request(&mut cx, None).await;
12270            apply_additional_edits.await.unwrap();
12271        }
12272    }
12273}
12274
12275#[gpui::test]
12276async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12277    init_test(cx, |_| {});
12278    let mut cx = EditorLspTestContext::new_rust(
12279        lsp::ServerCapabilities {
12280            completion_provider: Some(lsp::CompletionOptions {
12281                resolve_provider: Some(true),
12282                ..Default::default()
12283            }),
12284            ..Default::default()
12285        },
12286        cx,
12287    )
12288    .await;
12289
12290    let initial_state = "SubˇError";
12291    let buffer_marked_text = "<Sub|Error>";
12292    let completion_text = "SubscriptionError";
12293    let expected_with_insert_mode = "SubscriptionErrorˇError";
12294    let expected_with_replace_mode = "SubscriptionErrorˇ";
12295
12296    update_test_language_settings(&mut cx, |settings| {
12297        settings.defaults.completions = Some(CompletionSettings {
12298            words: WordsCompletionMode::Disabled,
12299            words_min_length: 0,
12300            // set the opposite here to ensure that the action is overriding the default behavior
12301            lsp_insert_mode: LspInsertMode::Insert,
12302            lsp: true,
12303            lsp_fetch_timeout_ms: 0,
12304        });
12305    });
12306
12307    cx.set_state(initial_state);
12308    cx.update_editor(|editor, window, cx| {
12309        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12310    });
12311
12312    let counter = Arc::new(AtomicUsize::new(0));
12313    handle_completion_request_with_insert_and_replace(
12314        &mut cx,
12315        buffer_marked_text,
12316        vec![(completion_text, completion_text)],
12317        counter.clone(),
12318    )
12319    .await;
12320    cx.condition(|editor, _| editor.context_menu_visible())
12321        .await;
12322    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12323
12324    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12325        editor
12326            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12327            .unwrap()
12328    });
12329    cx.assert_editor_state(expected_with_replace_mode);
12330    handle_resolve_completion_request(&mut cx, None).await;
12331    apply_additional_edits.await.unwrap();
12332
12333    update_test_language_settings(&mut cx, |settings| {
12334        settings.defaults.completions = Some(CompletionSettings {
12335            words: WordsCompletionMode::Disabled,
12336            words_min_length: 0,
12337            // set the opposite here to ensure that the action is overriding the default behavior
12338            lsp_insert_mode: LspInsertMode::Replace,
12339            lsp: true,
12340            lsp_fetch_timeout_ms: 0,
12341        });
12342    });
12343
12344    cx.set_state(initial_state);
12345    cx.update_editor(|editor, window, cx| {
12346        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12347    });
12348    handle_completion_request_with_insert_and_replace(
12349        &mut cx,
12350        buffer_marked_text,
12351        vec![(completion_text, completion_text)],
12352        counter.clone(),
12353    )
12354    .await;
12355    cx.condition(|editor, _| editor.context_menu_visible())
12356        .await;
12357    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12358
12359    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12360        editor
12361            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12362            .unwrap()
12363    });
12364    cx.assert_editor_state(expected_with_insert_mode);
12365    handle_resolve_completion_request(&mut cx, None).await;
12366    apply_additional_edits.await.unwrap();
12367}
12368
12369#[gpui::test]
12370async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12371    init_test(cx, |_| {});
12372    let mut cx = EditorLspTestContext::new_rust(
12373        lsp::ServerCapabilities {
12374            completion_provider: Some(lsp::CompletionOptions {
12375                resolve_provider: Some(true),
12376                ..Default::default()
12377            }),
12378            ..Default::default()
12379        },
12380        cx,
12381    )
12382    .await;
12383
12384    // scenario: surrounding text matches completion text
12385    let completion_text = "to_offset";
12386    let initial_state = indoc! {"
12387        1. buf.to_offˇsuffix
12388        2. buf.to_offˇsuf
12389        3. buf.to_offˇfix
12390        4. buf.to_offˇ
12391        5. into_offˇensive
12392        6. ˇsuffix
12393        7. let ˇ //
12394        8. aaˇzz
12395        9. buf.to_off«zzzzzˇ»suffix
12396        10. buf.«ˇzzzzz»suffix
12397        11. to_off«ˇzzzzz»
12398
12399        buf.to_offˇsuffix  // newest cursor
12400    "};
12401    let completion_marked_buffer = indoc! {"
12402        1. buf.to_offsuffix
12403        2. buf.to_offsuf
12404        3. buf.to_offfix
12405        4. buf.to_off
12406        5. into_offensive
12407        6. suffix
12408        7. let  //
12409        8. aazz
12410        9. buf.to_offzzzzzsuffix
12411        10. buf.zzzzzsuffix
12412        11. to_offzzzzz
12413
12414        buf.<to_off|suffix>  // newest cursor
12415    "};
12416    let expected = indoc! {"
12417        1. buf.to_offsetˇ
12418        2. buf.to_offsetˇsuf
12419        3. buf.to_offsetˇfix
12420        4. buf.to_offsetˇ
12421        5. into_offsetˇensive
12422        6. to_offsetˇsuffix
12423        7. let to_offsetˇ //
12424        8. aato_offsetˇzz
12425        9. buf.to_offsetˇ
12426        10. buf.to_offsetˇsuffix
12427        11. to_offsetˇ
12428
12429        buf.to_offsetˇ  // newest cursor
12430    "};
12431    cx.set_state(initial_state);
12432    cx.update_editor(|editor, window, cx| {
12433        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12434    });
12435    handle_completion_request_with_insert_and_replace(
12436        &mut cx,
12437        completion_marked_buffer,
12438        vec![(completion_text, completion_text)],
12439        Arc::new(AtomicUsize::new(0)),
12440    )
12441    .await;
12442    cx.condition(|editor, _| editor.context_menu_visible())
12443        .await;
12444    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12445        editor
12446            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12447            .unwrap()
12448    });
12449    cx.assert_editor_state(expected);
12450    handle_resolve_completion_request(&mut cx, None).await;
12451    apply_additional_edits.await.unwrap();
12452
12453    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12454    let completion_text = "foo_and_bar";
12455    let initial_state = indoc! {"
12456        1. ooanbˇ
12457        2. zooanbˇ
12458        3. ooanbˇz
12459        4. zooanbˇz
12460        5. ooanˇ
12461        6. oanbˇ
12462
12463        ooanbˇ
12464    "};
12465    let completion_marked_buffer = indoc! {"
12466        1. ooanb
12467        2. zooanb
12468        3. ooanbz
12469        4. zooanbz
12470        5. ooan
12471        6. oanb
12472
12473        <ooanb|>
12474    "};
12475    let expected = indoc! {"
12476        1. foo_and_barˇ
12477        2. zfoo_and_barˇ
12478        3. foo_and_barˇz
12479        4. zfoo_and_barˇz
12480        5. ooanfoo_and_barˇ
12481        6. oanbfoo_and_barˇ
12482
12483        foo_and_barˇ
12484    "};
12485    cx.set_state(initial_state);
12486    cx.update_editor(|editor, window, cx| {
12487        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12488    });
12489    handle_completion_request_with_insert_and_replace(
12490        &mut cx,
12491        completion_marked_buffer,
12492        vec![(completion_text, completion_text)],
12493        Arc::new(AtomicUsize::new(0)),
12494    )
12495    .await;
12496    cx.condition(|editor, _| editor.context_menu_visible())
12497        .await;
12498    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12499        editor
12500            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12501            .unwrap()
12502    });
12503    cx.assert_editor_state(expected);
12504    handle_resolve_completion_request(&mut cx, None).await;
12505    apply_additional_edits.await.unwrap();
12506
12507    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12508    // (expects the same as if it was inserted at the end)
12509    let completion_text = "foo_and_bar";
12510    let initial_state = indoc! {"
12511        1. ooˇanb
12512        2. zooˇanb
12513        3. ooˇanbz
12514        4. zooˇanbz
12515
12516        ooˇanb
12517    "};
12518    let completion_marked_buffer = indoc! {"
12519        1. ooanb
12520        2. zooanb
12521        3. ooanbz
12522        4. zooanbz
12523
12524        <oo|anb>
12525    "};
12526    let expected = indoc! {"
12527        1. foo_and_barˇ
12528        2. zfoo_and_barˇ
12529        3. foo_and_barˇz
12530        4. zfoo_and_barˇz
12531
12532        foo_and_barˇ
12533    "};
12534    cx.set_state(initial_state);
12535    cx.update_editor(|editor, window, cx| {
12536        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12537    });
12538    handle_completion_request_with_insert_and_replace(
12539        &mut cx,
12540        completion_marked_buffer,
12541        vec![(completion_text, completion_text)],
12542        Arc::new(AtomicUsize::new(0)),
12543    )
12544    .await;
12545    cx.condition(|editor, _| editor.context_menu_visible())
12546        .await;
12547    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12548        editor
12549            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12550            .unwrap()
12551    });
12552    cx.assert_editor_state(expected);
12553    handle_resolve_completion_request(&mut cx, None).await;
12554    apply_additional_edits.await.unwrap();
12555}
12556
12557// This used to crash
12558#[gpui::test]
12559async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12560    init_test(cx, |_| {});
12561
12562    let buffer_text = indoc! {"
12563        fn main() {
12564            10.satu;
12565
12566            //
12567            // separate cursors so they open in different excerpts (manually reproducible)
12568            //
12569
12570            10.satu20;
12571        }
12572    "};
12573    let multibuffer_text_with_selections = indoc! {"
12574        fn main() {
12575            10.satuˇ;
12576
12577            //
12578
12579            //
12580
12581            10.satuˇ20;
12582        }
12583    "};
12584    let expected_multibuffer = indoc! {"
12585        fn main() {
12586            10.saturating_sub()ˇ;
12587
12588            //
12589
12590            //
12591
12592            10.saturating_sub()ˇ;
12593        }
12594    "};
12595
12596    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12597    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12598
12599    let fs = FakeFs::new(cx.executor());
12600    fs.insert_tree(
12601        path!("/a"),
12602        json!({
12603            "main.rs": buffer_text,
12604        }),
12605    )
12606    .await;
12607
12608    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12609    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12610    language_registry.add(rust_lang());
12611    let mut fake_servers = language_registry.register_fake_lsp(
12612        "Rust",
12613        FakeLspAdapter {
12614            capabilities: lsp::ServerCapabilities {
12615                completion_provider: Some(lsp::CompletionOptions {
12616                    resolve_provider: None,
12617                    ..lsp::CompletionOptions::default()
12618                }),
12619                ..lsp::ServerCapabilities::default()
12620            },
12621            ..FakeLspAdapter::default()
12622        },
12623    );
12624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12625    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12626    let buffer = project
12627        .update(cx, |project, cx| {
12628            project.open_local_buffer(path!("/a/main.rs"), cx)
12629        })
12630        .await
12631        .unwrap();
12632
12633    let multi_buffer = cx.new(|cx| {
12634        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12635        multi_buffer.push_excerpts(
12636            buffer.clone(),
12637            [ExcerptRange::new(0..first_excerpt_end)],
12638            cx,
12639        );
12640        multi_buffer.push_excerpts(
12641            buffer.clone(),
12642            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12643            cx,
12644        );
12645        multi_buffer
12646    });
12647
12648    let editor = workspace
12649        .update(cx, |_, window, cx| {
12650            cx.new(|cx| {
12651                Editor::new(
12652                    EditorMode::Full {
12653                        scale_ui_elements_with_buffer_font_size: false,
12654                        show_active_line_background: false,
12655                        sized_by_content: false,
12656                    },
12657                    multi_buffer.clone(),
12658                    Some(project.clone()),
12659                    window,
12660                    cx,
12661                )
12662            })
12663        })
12664        .unwrap();
12665
12666    let pane = workspace
12667        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12668        .unwrap();
12669    pane.update_in(cx, |pane, window, cx| {
12670        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12671    });
12672
12673    let fake_server = fake_servers.next().await.unwrap();
12674
12675    editor.update_in(cx, |editor, window, cx| {
12676        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12677            s.select_ranges([
12678                Point::new(1, 11)..Point::new(1, 11),
12679                Point::new(7, 11)..Point::new(7, 11),
12680            ])
12681        });
12682
12683        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12684    });
12685
12686    editor.update_in(cx, |editor, window, cx| {
12687        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12688    });
12689
12690    fake_server
12691        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12692            let completion_item = lsp::CompletionItem {
12693                label: "saturating_sub()".into(),
12694                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12695                    lsp::InsertReplaceEdit {
12696                        new_text: "saturating_sub()".to_owned(),
12697                        insert: lsp::Range::new(
12698                            lsp::Position::new(7, 7),
12699                            lsp::Position::new(7, 11),
12700                        ),
12701                        replace: lsp::Range::new(
12702                            lsp::Position::new(7, 7),
12703                            lsp::Position::new(7, 13),
12704                        ),
12705                    },
12706                )),
12707                ..lsp::CompletionItem::default()
12708            };
12709
12710            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12711        })
12712        .next()
12713        .await
12714        .unwrap();
12715
12716    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12717        .await;
12718
12719    editor
12720        .update_in(cx, |editor, window, cx| {
12721            editor
12722                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12723                .unwrap()
12724        })
12725        .await
12726        .unwrap();
12727
12728    editor.update(cx, |editor, cx| {
12729        assert_text_with_selections(editor, expected_multibuffer, cx);
12730    })
12731}
12732
12733#[gpui::test]
12734async fn test_completion(cx: &mut TestAppContext) {
12735    init_test(cx, |_| {});
12736
12737    let mut cx = EditorLspTestContext::new_rust(
12738        lsp::ServerCapabilities {
12739            completion_provider: Some(lsp::CompletionOptions {
12740                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12741                resolve_provider: Some(true),
12742                ..Default::default()
12743            }),
12744            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12745            ..Default::default()
12746        },
12747        cx,
12748    )
12749    .await;
12750    let counter = Arc::new(AtomicUsize::new(0));
12751
12752    cx.set_state(indoc! {"
12753        oneˇ
12754        two
12755        three
12756    "});
12757    cx.simulate_keystroke(".");
12758    handle_completion_request(
12759        indoc! {"
12760            one.|<>
12761            two
12762            three
12763        "},
12764        vec!["first_completion", "second_completion"],
12765        true,
12766        counter.clone(),
12767        &mut cx,
12768    )
12769    .await;
12770    cx.condition(|editor, _| editor.context_menu_visible())
12771        .await;
12772    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12773
12774    let _handler = handle_signature_help_request(
12775        &mut cx,
12776        lsp::SignatureHelp {
12777            signatures: vec![lsp::SignatureInformation {
12778                label: "test signature".to_string(),
12779                documentation: None,
12780                parameters: Some(vec![lsp::ParameterInformation {
12781                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12782                    documentation: None,
12783                }]),
12784                active_parameter: None,
12785            }],
12786            active_signature: None,
12787            active_parameter: None,
12788        },
12789    );
12790    cx.update_editor(|editor, window, cx| {
12791        assert!(
12792            !editor.signature_help_state.is_shown(),
12793            "No signature help was called for"
12794        );
12795        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12796    });
12797    cx.run_until_parked();
12798    cx.update_editor(|editor, _, _| {
12799        assert!(
12800            !editor.signature_help_state.is_shown(),
12801            "No signature help should be shown when completions menu is open"
12802        );
12803    });
12804
12805    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12806        editor.context_menu_next(&Default::default(), window, cx);
12807        editor
12808            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12809            .unwrap()
12810    });
12811    cx.assert_editor_state(indoc! {"
12812        one.second_completionˇ
12813        two
12814        three
12815    "});
12816
12817    handle_resolve_completion_request(
12818        &mut cx,
12819        Some(vec![
12820            (
12821                //This overlaps with the primary completion edit which is
12822                //misbehavior from the LSP spec, test that we filter it out
12823                indoc! {"
12824                    one.second_ˇcompletion
12825                    two
12826                    threeˇ
12827                "},
12828                "overlapping additional edit",
12829            ),
12830            (
12831                indoc! {"
12832                    one.second_completion
12833                    two
12834                    threeˇ
12835                "},
12836                "\nadditional edit",
12837            ),
12838        ]),
12839    )
12840    .await;
12841    apply_additional_edits.await.unwrap();
12842    cx.assert_editor_state(indoc! {"
12843        one.second_completionˇ
12844        two
12845        three
12846        additional edit
12847    "});
12848
12849    cx.set_state(indoc! {"
12850        one.second_completion
12851        twoˇ
12852        threeˇ
12853        additional edit
12854    "});
12855    cx.simulate_keystroke(" ");
12856    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12857    cx.simulate_keystroke("s");
12858    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12859
12860    cx.assert_editor_state(indoc! {"
12861        one.second_completion
12862        two sˇ
12863        three sˇ
12864        additional edit
12865    "});
12866    handle_completion_request(
12867        indoc! {"
12868            one.second_completion
12869            two s
12870            three <s|>
12871            additional edit
12872        "},
12873        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12874        true,
12875        counter.clone(),
12876        &mut cx,
12877    )
12878    .await;
12879    cx.condition(|editor, _| editor.context_menu_visible())
12880        .await;
12881    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12882
12883    cx.simulate_keystroke("i");
12884
12885    handle_completion_request(
12886        indoc! {"
12887            one.second_completion
12888            two si
12889            three <si|>
12890            additional edit
12891        "},
12892        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12893        true,
12894        counter.clone(),
12895        &mut cx,
12896    )
12897    .await;
12898    cx.condition(|editor, _| editor.context_menu_visible())
12899        .await;
12900    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
12901
12902    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12903        editor
12904            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12905            .unwrap()
12906    });
12907    cx.assert_editor_state(indoc! {"
12908        one.second_completion
12909        two sixth_completionˇ
12910        three sixth_completionˇ
12911        additional edit
12912    "});
12913
12914    apply_additional_edits.await.unwrap();
12915
12916    update_test_language_settings(&mut cx, |settings| {
12917        settings.defaults.show_completions_on_input = Some(false);
12918    });
12919    cx.set_state("editorˇ");
12920    cx.simulate_keystroke(".");
12921    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12922    cx.simulate_keystrokes("c l o");
12923    cx.assert_editor_state("editor.cloˇ");
12924    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12925    cx.update_editor(|editor, window, cx| {
12926        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12927    });
12928    handle_completion_request(
12929        "editor.<clo|>",
12930        vec!["close", "clobber"],
12931        true,
12932        counter.clone(),
12933        &mut cx,
12934    )
12935    .await;
12936    cx.condition(|editor, _| editor.context_menu_visible())
12937        .await;
12938    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
12939
12940    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12941        editor
12942            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12943            .unwrap()
12944    });
12945    cx.assert_editor_state("editor.clobberˇ");
12946    handle_resolve_completion_request(&mut cx, None).await;
12947    apply_additional_edits.await.unwrap();
12948}
12949
12950#[gpui::test]
12951async fn test_completion_reuse(cx: &mut TestAppContext) {
12952    init_test(cx, |_| {});
12953
12954    let mut cx = EditorLspTestContext::new_rust(
12955        lsp::ServerCapabilities {
12956            completion_provider: Some(lsp::CompletionOptions {
12957                trigger_characters: Some(vec![".".to_string()]),
12958                ..Default::default()
12959            }),
12960            ..Default::default()
12961        },
12962        cx,
12963    )
12964    .await;
12965
12966    let counter = Arc::new(AtomicUsize::new(0));
12967    cx.set_state("objˇ");
12968    cx.simulate_keystroke(".");
12969
12970    // Initial completion request returns complete results
12971    let is_incomplete = false;
12972    handle_completion_request(
12973        "obj.|<>",
12974        vec!["a", "ab", "abc"],
12975        is_incomplete,
12976        counter.clone(),
12977        &mut cx,
12978    )
12979    .await;
12980    cx.run_until_parked();
12981    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12982    cx.assert_editor_state("obj.ˇ");
12983    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12984
12985    // Type "a" - filters existing completions
12986    cx.simulate_keystroke("a");
12987    cx.run_until_parked();
12988    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12989    cx.assert_editor_state("obj.aˇ");
12990    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
12991
12992    // Type "b" - filters existing completions
12993    cx.simulate_keystroke("b");
12994    cx.run_until_parked();
12995    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12996    cx.assert_editor_state("obj.abˇ");
12997    check_displayed_completions(vec!["ab", "abc"], &mut cx);
12998
12999    // Type "c" - filters existing completions
13000    cx.simulate_keystroke("c");
13001    cx.run_until_parked();
13002    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13003    cx.assert_editor_state("obj.abcˇ");
13004    check_displayed_completions(vec!["abc"], &mut cx);
13005
13006    // Backspace to delete "c" - filters existing completions
13007    cx.update_editor(|editor, window, cx| {
13008        editor.backspace(&Backspace, window, cx);
13009    });
13010    cx.run_until_parked();
13011    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13012    cx.assert_editor_state("obj.abˇ");
13013    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13014
13015    // Moving cursor to the left dismisses menu.
13016    cx.update_editor(|editor, window, cx| {
13017        editor.move_left(&MoveLeft, window, cx);
13018    });
13019    cx.run_until_parked();
13020    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13021    cx.assert_editor_state("obj.aˇb");
13022    cx.update_editor(|editor, _, _| {
13023        assert_eq!(editor.context_menu_visible(), false);
13024    });
13025
13026    // Type "b" - new request
13027    cx.simulate_keystroke("b");
13028    let is_incomplete = false;
13029    handle_completion_request(
13030        "obj.<ab|>a",
13031        vec!["ab", "abc"],
13032        is_incomplete,
13033        counter.clone(),
13034        &mut cx,
13035    )
13036    .await;
13037    cx.run_until_parked();
13038    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13039    cx.assert_editor_state("obj.abˇb");
13040    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13041
13042    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13043    cx.update_editor(|editor, window, cx| {
13044        editor.backspace(&Backspace, window, cx);
13045    });
13046    let is_incomplete = false;
13047    handle_completion_request(
13048        "obj.<a|>b",
13049        vec!["a", "ab", "abc"],
13050        is_incomplete,
13051        counter.clone(),
13052        &mut cx,
13053    )
13054    .await;
13055    cx.run_until_parked();
13056    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13057    cx.assert_editor_state("obj.aˇb");
13058    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13059
13060    // Backspace to delete "a" - dismisses menu.
13061    cx.update_editor(|editor, window, cx| {
13062        editor.backspace(&Backspace, window, cx);
13063    });
13064    cx.run_until_parked();
13065    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13066    cx.assert_editor_state("obj.ˇb");
13067    cx.update_editor(|editor, _, _| {
13068        assert_eq!(editor.context_menu_visible(), false);
13069    });
13070}
13071
13072#[gpui::test]
13073async fn test_word_completion(cx: &mut TestAppContext) {
13074    let lsp_fetch_timeout_ms = 10;
13075    init_test(cx, |language_settings| {
13076        language_settings.defaults.completions = Some(CompletionSettings {
13077            words: WordsCompletionMode::Fallback,
13078            words_min_length: 0,
13079            lsp: true,
13080            lsp_fetch_timeout_ms: 10,
13081            lsp_insert_mode: LspInsertMode::Insert,
13082        });
13083    });
13084
13085    let mut cx = EditorLspTestContext::new_rust(
13086        lsp::ServerCapabilities {
13087            completion_provider: Some(lsp::CompletionOptions {
13088                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13089                ..lsp::CompletionOptions::default()
13090            }),
13091            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13092            ..lsp::ServerCapabilities::default()
13093        },
13094        cx,
13095    )
13096    .await;
13097
13098    let throttle_completions = Arc::new(AtomicBool::new(false));
13099
13100    let lsp_throttle_completions = throttle_completions.clone();
13101    let _completion_requests_handler =
13102        cx.lsp
13103            .server
13104            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13105                let lsp_throttle_completions = lsp_throttle_completions.clone();
13106                let cx = cx.clone();
13107                async move {
13108                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13109                        cx.background_executor()
13110                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13111                            .await;
13112                    }
13113                    Ok(Some(lsp::CompletionResponse::Array(vec![
13114                        lsp::CompletionItem {
13115                            label: "first".into(),
13116                            ..lsp::CompletionItem::default()
13117                        },
13118                        lsp::CompletionItem {
13119                            label: "last".into(),
13120                            ..lsp::CompletionItem::default()
13121                        },
13122                    ])))
13123                }
13124            });
13125
13126    cx.set_state(indoc! {"
13127        oneˇ
13128        two
13129        three
13130    "});
13131    cx.simulate_keystroke(".");
13132    cx.executor().run_until_parked();
13133    cx.condition(|editor, _| editor.context_menu_visible())
13134        .await;
13135    cx.update_editor(|editor, window, cx| {
13136        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13137        {
13138            assert_eq!(
13139                completion_menu_entries(menu),
13140                &["first", "last"],
13141                "When LSP server is fast to reply, no fallback word completions are used"
13142            );
13143        } else {
13144            panic!("expected completion menu to be open");
13145        }
13146        editor.cancel(&Cancel, window, cx);
13147    });
13148    cx.executor().run_until_parked();
13149    cx.condition(|editor, _| !editor.context_menu_visible())
13150        .await;
13151
13152    throttle_completions.store(true, atomic::Ordering::Release);
13153    cx.simulate_keystroke(".");
13154    cx.executor()
13155        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
13156    cx.executor().run_until_parked();
13157    cx.condition(|editor, _| editor.context_menu_visible())
13158        .await;
13159    cx.update_editor(|editor, _, _| {
13160        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13161        {
13162            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
13163                "When LSP server is slow, document words can be shown instead, if configured accordingly");
13164        } else {
13165            panic!("expected completion menu to be open");
13166        }
13167    });
13168}
13169
13170#[gpui::test]
13171async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
13172    init_test(cx, |language_settings| {
13173        language_settings.defaults.completions = Some(CompletionSettings {
13174            words: WordsCompletionMode::Enabled,
13175            words_min_length: 0,
13176            lsp: true,
13177            lsp_fetch_timeout_ms: 0,
13178            lsp_insert_mode: LspInsertMode::Insert,
13179        });
13180    });
13181
13182    let mut cx = EditorLspTestContext::new_rust(
13183        lsp::ServerCapabilities {
13184            completion_provider: Some(lsp::CompletionOptions {
13185                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13186                ..lsp::CompletionOptions::default()
13187            }),
13188            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13189            ..lsp::ServerCapabilities::default()
13190        },
13191        cx,
13192    )
13193    .await;
13194
13195    let _completion_requests_handler =
13196        cx.lsp
13197            .server
13198            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13199                Ok(Some(lsp::CompletionResponse::Array(vec![
13200                    lsp::CompletionItem {
13201                        label: "first".into(),
13202                        ..lsp::CompletionItem::default()
13203                    },
13204                    lsp::CompletionItem {
13205                        label: "last".into(),
13206                        ..lsp::CompletionItem::default()
13207                    },
13208                ])))
13209            });
13210
13211    cx.set_state(indoc! {"ˇ
13212        first
13213        last
13214        second
13215    "});
13216    cx.simulate_keystroke(".");
13217    cx.executor().run_until_parked();
13218    cx.condition(|editor, _| editor.context_menu_visible())
13219        .await;
13220    cx.update_editor(|editor, _, _| {
13221        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13222        {
13223            assert_eq!(
13224                completion_menu_entries(menu),
13225                &["first", "last", "second"],
13226                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13227            );
13228        } else {
13229            panic!("expected completion menu to be open");
13230        }
13231    });
13232}
13233
13234#[gpui::test]
13235async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13236    init_test(cx, |language_settings| {
13237        language_settings.defaults.completions = Some(CompletionSettings {
13238            words: WordsCompletionMode::Disabled,
13239            words_min_length: 0,
13240            lsp: true,
13241            lsp_fetch_timeout_ms: 0,
13242            lsp_insert_mode: LspInsertMode::Insert,
13243        });
13244    });
13245
13246    let mut cx = EditorLspTestContext::new_rust(
13247        lsp::ServerCapabilities {
13248            completion_provider: Some(lsp::CompletionOptions {
13249                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13250                ..lsp::CompletionOptions::default()
13251            }),
13252            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13253            ..lsp::ServerCapabilities::default()
13254        },
13255        cx,
13256    )
13257    .await;
13258
13259    let _completion_requests_handler =
13260        cx.lsp
13261            .server
13262            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13263                panic!("LSP completions should not be queried when dealing with word completions")
13264            });
13265
13266    cx.set_state(indoc! {"ˇ
13267        first
13268        last
13269        second
13270    "});
13271    cx.update_editor(|editor, window, cx| {
13272        editor.show_word_completions(&ShowWordCompletions, window, cx);
13273    });
13274    cx.executor().run_until_parked();
13275    cx.condition(|editor, _| editor.context_menu_visible())
13276        .await;
13277    cx.update_editor(|editor, _, _| {
13278        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13279        {
13280            assert_eq!(
13281                completion_menu_entries(menu),
13282                &["first", "last", "second"],
13283                "`ShowWordCompletions` action should show word completions"
13284            );
13285        } else {
13286            panic!("expected completion menu to be open");
13287        }
13288    });
13289
13290    cx.simulate_keystroke("l");
13291    cx.executor().run_until_parked();
13292    cx.condition(|editor, _| editor.context_menu_visible())
13293        .await;
13294    cx.update_editor(|editor, _, _| {
13295        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13296        {
13297            assert_eq!(
13298                completion_menu_entries(menu),
13299                &["last"],
13300                "After showing word completions, further editing should filter them and not query the LSP"
13301            );
13302        } else {
13303            panic!("expected completion menu to be open");
13304        }
13305    });
13306}
13307
13308#[gpui::test]
13309async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13310    init_test(cx, |language_settings| {
13311        language_settings.defaults.completions = Some(CompletionSettings {
13312            words: WordsCompletionMode::Fallback,
13313            words_min_length: 0,
13314            lsp: false,
13315            lsp_fetch_timeout_ms: 0,
13316            lsp_insert_mode: LspInsertMode::Insert,
13317        });
13318    });
13319
13320    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13321
13322    cx.set_state(indoc! {"ˇ
13323        0_usize
13324        let
13325        33
13326        4.5f32
13327    "});
13328    cx.update_editor(|editor, window, cx| {
13329        editor.show_completions(&ShowCompletions::default(), window, cx);
13330    });
13331    cx.executor().run_until_parked();
13332    cx.condition(|editor, _| editor.context_menu_visible())
13333        .await;
13334    cx.update_editor(|editor, window, cx| {
13335        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13336        {
13337            assert_eq!(
13338                completion_menu_entries(menu),
13339                &["let"],
13340                "With no digits in the completion query, no digits should be in the word completions"
13341            );
13342        } else {
13343            panic!("expected completion menu to be open");
13344        }
13345        editor.cancel(&Cancel, window, cx);
13346    });
13347
13348    cx.set_state(indoc! {"13349        0_usize
13350        let
13351        3
13352        33.35f32
13353    "});
13354    cx.update_editor(|editor, window, cx| {
13355        editor.show_completions(&ShowCompletions::default(), window, cx);
13356    });
13357    cx.executor().run_until_parked();
13358    cx.condition(|editor, _| editor.context_menu_visible())
13359        .await;
13360    cx.update_editor(|editor, _, _| {
13361        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13362        {
13363            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
13364                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13365        } else {
13366            panic!("expected completion menu to be open");
13367        }
13368    });
13369}
13370
13371#[gpui::test]
13372async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
13373    init_test(cx, |language_settings| {
13374        language_settings.defaults.completions = Some(CompletionSettings {
13375            words: WordsCompletionMode::Enabled,
13376            words_min_length: 3,
13377            lsp: true,
13378            lsp_fetch_timeout_ms: 0,
13379            lsp_insert_mode: LspInsertMode::Insert,
13380        });
13381    });
13382
13383    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13384    cx.set_state(indoc! {"ˇ
13385        wow
13386        wowen
13387        wowser
13388    "});
13389    cx.simulate_keystroke("w");
13390    cx.executor().run_until_parked();
13391    cx.update_editor(|editor, _, _| {
13392        if editor.context_menu.borrow_mut().is_some() {
13393            panic!(
13394                "expected completion menu to be hidden, as words completion threshold is not met"
13395            );
13396        }
13397    });
13398
13399    cx.simulate_keystroke("o");
13400    cx.executor().run_until_parked();
13401    cx.update_editor(|editor, _, _| {
13402        if editor.context_menu.borrow_mut().is_some() {
13403            panic!(
13404                "expected completion menu to be hidden, as words completion threshold is not met still"
13405            );
13406        }
13407    });
13408
13409    cx.simulate_keystroke("w");
13410    cx.executor().run_until_parked();
13411    cx.update_editor(|editor, _, _| {
13412        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13413        {
13414            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
13415        } else {
13416            panic!("expected completion menu to be open after the word completions threshold is met");
13417        }
13418    });
13419}
13420
13421fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13422    let position = || lsp::Position {
13423        line: params.text_document_position.position.line,
13424        character: params.text_document_position.position.character,
13425    };
13426    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13427        range: lsp::Range {
13428            start: position(),
13429            end: position(),
13430        },
13431        new_text: text.to_string(),
13432    }))
13433}
13434
13435#[gpui::test]
13436async fn test_multiline_completion(cx: &mut TestAppContext) {
13437    init_test(cx, |_| {});
13438
13439    let fs = FakeFs::new(cx.executor());
13440    fs.insert_tree(
13441        path!("/a"),
13442        json!({
13443            "main.ts": "a",
13444        }),
13445    )
13446    .await;
13447
13448    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13449    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13450    let typescript_language = Arc::new(Language::new(
13451        LanguageConfig {
13452            name: "TypeScript".into(),
13453            matcher: LanguageMatcher {
13454                path_suffixes: vec!["ts".to_string()],
13455                ..LanguageMatcher::default()
13456            },
13457            line_comments: vec!["// ".into()],
13458            ..LanguageConfig::default()
13459        },
13460        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13461    ));
13462    language_registry.add(typescript_language.clone());
13463    let mut fake_servers = language_registry.register_fake_lsp(
13464        "TypeScript",
13465        FakeLspAdapter {
13466            capabilities: lsp::ServerCapabilities {
13467                completion_provider: Some(lsp::CompletionOptions {
13468                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13469                    ..lsp::CompletionOptions::default()
13470                }),
13471                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13472                ..lsp::ServerCapabilities::default()
13473            },
13474            // Emulate vtsls label generation
13475            label_for_completion: Some(Box::new(|item, _| {
13476                let text = if let Some(description) = item
13477                    .label_details
13478                    .as_ref()
13479                    .and_then(|label_details| label_details.description.as_ref())
13480                {
13481                    format!("{} {}", item.label, description)
13482                } else if let Some(detail) = &item.detail {
13483                    format!("{} {}", item.label, detail)
13484                } else {
13485                    item.label.clone()
13486                };
13487                let len = text.len();
13488                Some(language::CodeLabel {
13489                    text,
13490                    runs: Vec::new(),
13491                    filter_range: 0..len,
13492                })
13493            })),
13494            ..FakeLspAdapter::default()
13495        },
13496    );
13497    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13498    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13499    let worktree_id = workspace
13500        .update(cx, |workspace, _window, cx| {
13501            workspace.project().update(cx, |project, cx| {
13502                project.worktrees(cx).next().unwrap().read(cx).id()
13503            })
13504        })
13505        .unwrap();
13506    let _buffer = project
13507        .update(cx, |project, cx| {
13508            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13509        })
13510        .await
13511        .unwrap();
13512    let editor = workspace
13513        .update(cx, |workspace, window, cx| {
13514            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13515        })
13516        .unwrap()
13517        .await
13518        .unwrap()
13519        .downcast::<Editor>()
13520        .unwrap();
13521    let fake_server = fake_servers.next().await.unwrap();
13522
13523    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13524    let multiline_label_2 = "a\nb\nc\n";
13525    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13526    let multiline_description = "d\ne\nf\n";
13527    let multiline_detail_2 = "g\nh\ni\n";
13528
13529    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13530        move |params, _| async move {
13531            Ok(Some(lsp::CompletionResponse::Array(vec![
13532                lsp::CompletionItem {
13533                    label: multiline_label.to_string(),
13534                    text_edit: gen_text_edit(&params, "new_text_1"),
13535                    ..lsp::CompletionItem::default()
13536                },
13537                lsp::CompletionItem {
13538                    label: "single line label 1".to_string(),
13539                    detail: Some(multiline_detail.to_string()),
13540                    text_edit: gen_text_edit(&params, "new_text_2"),
13541                    ..lsp::CompletionItem::default()
13542                },
13543                lsp::CompletionItem {
13544                    label: "single line label 2".to_string(),
13545                    label_details: Some(lsp::CompletionItemLabelDetails {
13546                        description: Some(multiline_description.to_string()),
13547                        detail: None,
13548                    }),
13549                    text_edit: gen_text_edit(&params, "new_text_2"),
13550                    ..lsp::CompletionItem::default()
13551                },
13552                lsp::CompletionItem {
13553                    label: multiline_label_2.to_string(),
13554                    detail: Some(multiline_detail_2.to_string()),
13555                    text_edit: gen_text_edit(&params, "new_text_3"),
13556                    ..lsp::CompletionItem::default()
13557                },
13558                lsp::CompletionItem {
13559                    label: "Label with many     spaces and \t but without newlines".to_string(),
13560                    detail: Some(
13561                        "Details with many     spaces and \t but without newlines".to_string(),
13562                    ),
13563                    text_edit: gen_text_edit(&params, "new_text_4"),
13564                    ..lsp::CompletionItem::default()
13565                },
13566            ])))
13567        },
13568    );
13569
13570    editor.update_in(cx, |editor, window, cx| {
13571        cx.focus_self(window);
13572        editor.move_to_end(&MoveToEnd, window, cx);
13573        editor.handle_input(".", window, cx);
13574    });
13575    cx.run_until_parked();
13576    completion_handle.next().await.unwrap();
13577
13578    editor.update(cx, |editor, _| {
13579        assert!(editor.context_menu_visible());
13580        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13581        {
13582            let completion_labels = menu
13583                .completions
13584                .borrow()
13585                .iter()
13586                .map(|c| c.label.text.clone())
13587                .collect::<Vec<_>>();
13588            assert_eq!(
13589                completion_labels,
13590                &[
13591                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13592                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13593                    "single line label 2 d e f ",
13594                    "a b c g h i ",
13595                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13596                ],
13597                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13598            );
13599
13600            for completion in menu
13601                .completions
13602                .borrow()
13603                .iter() {
13604                    assert_eq!(
13605                        completion.label.filter_range,
13606                        0..completion.label.text.len(),
13607                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13608                    );
13609                }
13610        } else {
13611            panic!("expected completion menu to be open");
13612        }
13613    });
13614}
13615
13616#[gpui::test]
13617async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13618    init_test(cx, |_| {});
13619    let mut cx = EditorLspTestContext::new_rust(
13620        lsp::ServerCapabilities {
13621            completion_provider: Some(lsp::CompletionOptions {
13622                trigger_characters: Some(vec![".".to_string()]),
13623                ..Default::default()
13624            }),
13625            ..Default::default()
13626        },
13627        cx,
13628    )
13629    .await;
13630    cx.lsp
13631        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13632            Ok(Some(lsp::CompletionResponse::Array(vec![
13633                lsp::CompletionItem {
13634                    label: "first".into(),
13635                    ..Default::default()
13636                },
13637                lsp::CompletionItem {
13638                    label: "last".into(),
13639                    ..Default::default()
13640                },
13641            ])))
13642        });
13643    cx.set_state("variableˇ");
13644    cx.simulate_keystroke(".");
13645    cx.executor().run_until_parked();
13646
13647    cx.update_editor(|editor, _, _| {
13648        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13649        {
13650            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
13651        } else {
13652            panic!("expected completion menu to be open");
13653        }
13654    });
13655
13656    cx.update_editor(|editor, window, cx| {
13657        editor.move_page_down(&MovePageDown::default(), window, cx);
13658        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13659        {
13660            assert!(
13661                menu.selected_item == 1,
13662                "expected PageDown to select the last item from the context menu"
13663            );
13664        } else {
13665            panic!("expected completion menu to stay open after PageDown");
13666        }
13667    });
13668
13669    cx.update_editor(|editor, window, cx| {
13670        editor.move_page_up(&MovePageUp::default(), window, cx);
13671        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13672        {
13673            assert!(
13674                menu.selected_item == 0,
13675                "expected PageUp to select the first item from the context menu"
13676            );
13677        } else {
13678            panic!("expected completion menu to stay open after PageUp");
13679        }
13680    });
13681}
13682
13683#[gpui::test]
13684async fn test_as_is_completions(cx: &mut TestAppContext) {
13685    init_test(cx, |_| {});
13686    let mut cx = EditorLspTestContext::new_rust(
13687        lsp::ServerCapabilities {
13688            completion_provider: Some(lsp::CompletionOptions {
13689                ..Default::default()
13690            }),
13691            ..Default::default()
13692        },
13693        cx,
13694    )
13695    .await;
13696    cx.lsp
13697        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13698            Ok(Some(lsp::CompletionResponse::Array(vec![
13699                lsp::CompletionItem {
13700                    label: "unsafe".into(),
13701                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13702                        range: lsp::Range {
13703                            start: lsp::Position {
13704                                line: 1,
13705                                character: 2,
13706                            },
13707                            end: lsp::Position {
13708                                line: 1,
13709                                character: 3,
13710                            },
13711                        },
13712                        new_text: "unsafe".to_string(),
13713                    })),
13714                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13715                    ..Default::default()
13716                },
13717            ])))
13718        });
13719    cx.set_state("fn a() {}\n");
13720    cx.executor().run_until_parked();
13721    cx.update_editor(|editor, window, cx| {
13722        editor.show_completions(
13723            &ShowCompletions {
13724                trigger: Some("\n".into()),
13725            },
13726            window,
13727            cx,
13728        );
13729    });
13730    cx.executor().run_until_parked();
13731
13732    cx.update_editor(|editor, window, cx| {
13733        editor.confirm_completion(&Default::default(), window, cx)
13734    });
13735    cx.executor().run_until_parked();
13736    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13737}
13738
13739#[gpui::test]
13740async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13741    init_test(cx, |_| {});
13742    let language =
13743        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13744    let mut cx = EditorLspTestContext::new(
13745        language,
13746        lsp::ServerCapabilities {
13747            completion_provider: Some(lsp::CompletionOptions {
13748                ..lsp::CompletionOptions::default()
13749            }),
13750            ..lsp::ServerCapabilities::default()
13751        },
13752        cx,
13753    )
13754    .await;
13755
13756    cx.set_state(
13757        "#ifndef BAR_H
13758#define BAR_H
13759
13760#include <stdbool.h>
13761
13762int fn_branch(bool do_branch1, bool do_branch2);
13763
13764#endif // BAR_H
13765ˇ",
13766    );
13767    cx.executor().run_until_parked();
13768    cx.update_editor(|editor, window, cx| {
13769        editor.handle_input("#", window, cx);
13770    });
13771    cx.executor().run_until_parked();
13772    cx.update_editor(|editor, window, cx| {
13773        editor.handle_input("i", window, cx);
13774    });
13775    cx.executor().run_until_parked();
13776    cx.update_editor(|editor, window, cx| {
13777        editor.handle_input("n", window, cx);
13778    });
13779    cx.executor().run_until_parked();
13780    cx.assert_editor_state(
13781        "#ifndef BAR_H
13782#define BAR_H
13783
13784#include <stdbool.h>
13785
13786int fn_branch(bool do_branch1, bool do_branch2);
13787
13788#endif // BAR_H
13789#inˇ",
13790    );
13791
13792    cx.lsp
13793        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13794            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13795                is_incomplete: false,
13796                item_defaults: None,
13797                items: vec![lsp::CompletionItem {
13798                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13799                    label_details: Some(lsp::CompletionItemLabelDetails {
13800                        detail: Some("header".to_string()),
13801                        description: None,
13802                    }),
13803                    label: " include".to_string(),
13804                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13805                        range: lsp::Range {
13806                            start: lsp::Position {
13807                                line: 8,
13808                                character: 1,
13809                            },
13810                            end: lsp::Position {
13811                                line: 8,
13812                                character: 1,
13813                            },
13814                        },
13815                        new_text: "include \"$0\"".to_string(),
13816                    })),
13817                    sort_text: Some("40b67681include".to_string()),
13818                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13819                    filter_text: Some("include".to_string()),
13820                    insert_text: Some("include \"$0\"".to_string()),
13821                    ..lsp::CompletionItem::default()
13822                }],
13823            })))
13824        });
13825    cx.update_editor(|editor, window, cx| {
13826        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13827    });
13828    cx.executor().run_until_parked();
13829    cx.update_editor(|editor, window, cx| {
13830        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13831    });
13832    cx.executor().run_until_parked();
13833    cx.assert_editor_state(
13834        "#ifndef BAR_H
13835#define BAR_H
13836
13837#include <stdbool.h>
13838
13839int fn_branch(bool do_branch1, bool do_branch2);
13840
13841#endif // BAR_H
13842#include \"ˇ\"",
13843    );
13844
13845    cx.lsp
13846        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13847            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13848                is_incomplete: true,
13849                item_defaults: None,
13850                items: vec![lsp::CompletionItem {
13851                    kind: Some(lsp::CompletionItemKind::FILE),
13852                    label: "AGL/".to_string(),
13853                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13854                        range: lsp::Range {
13855                            start: lsp::Position {
13856                                line: 8,
13857                                character: 10,
13858                            },
13859                            end: lsp::Position {
13860                                line: 8,
13861                                character: 11,
13862                            },
13863                        },
13864                        new_text: "AGL/".to_string(),
13865                    })),
13866                    sort_text: Some("40b67681AGL/".to_string()),
13867                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13868                    filter_text: Some("AGL/".to_string()),
13869                    insert_text: Some("AGL/".to_string()),
13870                    ..lsp::CompletionItem::default()
13871                }],
13872            })))
13873        });
13874    cx.update_editor(|editor, window, cx| {
13875        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13876    });
13877    cx.executor().run_until_parked();
13878    cx.update_editor(|editor, window, cx| {
13879        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13880    });
13881    cx.executor().run_until_parked();
13882    cx.assert_editor_state(
13883        r##"#ifndef BAR_H
13884#define BAR_H
13885
13886#include <stdbool.h>
13887
13888int fn_branch(bool do_branch1, bool do_branch2);
13889
13890#endif // BAR_H
13891#include "AGL/ˇ"##,
13892    );
13893
13894    cx.update_editor(|editor, window, cx| {
13895        editor.handle_input("\"", window, cx);
13896    });
13897    cx.executor().run_until_parked();
13898    cx.assert_editor_state(
13899        r##"#ifndef BAR_H
13900#define BAR_H
13901
13902#include <stdbool.h>
13903
13904int fn_branch(bool do_branch1, bool do_branch2);
13905
13906#endif // BAR_H
13907#include "AGL/"ˇ"##,
13908    );
13909}
13910
13911#[gpui::test]
13912async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
13913    init_test(cx, |_| {});
13914
13915    let mut cx = EditorLspTestContext::new_rust(
13916        lsp::ServerCapabilities {
13917            completion_provider: Some(lsp::CompletionOptions {
13918                trigger_characters: Some(vec![".".to_string()]),
13919                resolve_provider: Some(true),
13920                ..Default::default()
13921            }),
13922            ..Default::default()
13923        },
13924        cx,
13925    )
13926    .await;
13927
13928    cx.set_state("fn main() { let a = 2ˇ; }");
13929    cx.simulate_keystroke(".");
13930    let completion_item = lsp::CompletionItem {
13931        label: "Some".into(),
13932        kind: Some(lsp::CompletionItemKind::SNIPPET),
13933        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
13934        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
13935            kind: lsp::MarkupKind::Markdown,
13936            value: "```rust\nSome(2)\n```".to_string(),
13937        })),
13938        deprecated: Some(false),
13939        sort_text: Some("Some".to_string()),
13940        filter_text: Some("Some".to_string()),
13941        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13942        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13943            range: lsp::Range {
13944                start: lsp::Position {
13945                    line: 0,
13946                    character: 22,
13947                },
13948                end: lsp::Position {
13949                    line: 0,
13950                    character: 22,
13951                },
13952            },
13953            new_text: "Some(2)".to_string(),
13954        })),
13955        additional_text_edits: Some(vec![lsp::TextEdit {
13956            range: lsp::Range {
13957                start: lsp::Position {
13958                    line: 0,
13959                    character: 20,
13960                },
13961                end: lsp::Position {
13962                    line: 0,
13963                    character: 22,
13964                },
13965            },
13966            new_text: "".to_string(),
13967        }]),
13968        ..Default::default()
13969    };
13970
13971    let closure_completion_item = completion_item.clone();
13972    let counter = Arc::new(AtomicUsize::new(0));
13973    let counter_clone = counter.clone();
13974    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
13975        let task_completion_item = closure_completion_item.clone();
13976        counter_clone.fetch_add(1, atomic::Ordering::Release);
13977        async move {
13978            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13979                is_incomplete: true,
13980                item_defaults: None,
13981                items: vec![task_completion_item],
13982            })))
13983        }
13984    });
13985
13986    cx.condition(|editor, _| editor.context_menu_visible())
13987        .await;
13988    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
13989    assert!(request.next().await.is_some());
13990    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13991
13992    cx.simulate_keystrokes("S o m");
13993    cx.condition(|editor, _| editor.context_menu_visible())
13994        .await;
13995    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
13996    assert!(request.next().await.is_some());
13997    assert!(request.next().await.is_some());
13998    assert!(request.next().await.is_some());
13999    request.close();
14000    assert!(request.next().await.is_none());
14001    assert_eq!(
14002        counter.load(atomic::Ordering::Acquire),
14003        4,
14004        "With the completions menu open, only one LSP request should happen per input"
14005    );
14006}
14007
14008#[gpui::test]
14009async fn test_toggle_comment(cx: &mut TestAppContext) {
14010    init_test(cx, |_| {});
14011    let mut cx = EditorTestContext::new(cx).await;
14012    let language = Arc::new(Language::new(
14013        LanguageConfig {
14014            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14015            ..Default::default()
14016        },
14017        Some(tree_sitter_rust::LANGUAGE.into()),
14018    ));
14019    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14020
14021    // If multiple selections intersect a line, the line is only toggled once.
14022    cx.set_state(indoc! {"
14023        fn a() {
14024            «//b();
14025            ˇ»// «c();
14026            //ˇ»  d();
14027        }
14028    "});
14029
14030    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14031
14032    cx.assert_editor_state(indoc! {"
14033        fn a() {
14034            «b();
14035            c();
14036            ˇ» d();
14037        }
14038    "});
14039
14040    // The comment prefix is inserted at the same column for every line in a
14041    // selection.
14042    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14043
14044    cx.assert_editor_state(indoc! {"
14045        fn a() {
14046            // «b();
14047            // c();
14048            ˇ»//  d();
14049        }
14050    "});
14051
14052    // If a selection ends at the beginning of a line, that line is not toggled.
14053    cx.set_selections_state(indoc! {"
14054        fn a() {
14055            // b();
14056            «// c();
14057        ˇ»    //  d();
14058        }
14059    "});
14060
14061    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14062
14063    cx.assert_editor_state(indoc! {"
14064        fn a() {
14065            // b();
14066            «c();
14067        ˇ»    //  d();
14068        }
14069    "});
14070
14071    // If a selection span a single line and is empty, the line is toggled.
14072    cx.set_state(indoc! {"
14073        fn a() {
14074            a();
14075            b();
14076        ˇ
14077        }
14078    "});
14079
14080    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14081
14082    cx.assert_editor_state(indoc! {"
14083        fn a() {
14084            a();
14085            b();
14086        //•ˇ
14087        }
14088    "});
14089
14090    // If a selection span multiple lines, empty lines are not toggled.
14091    cx.set_state(indoc! {"
14092        fn a() {
14093            «a();
14094
14095            c();ˇ»
14096        }
14097    "});
14098
14099    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14100
14101    cx.assert_editor_state(indoc! {"
14102        fn a() {
14103            // «a();
14104
14105            // c();ˇ»
14106        }
14107    "});
14108
14109    // If a selection includes multiple comment prefixes, all lines are uncommented.
14110    cx.set_state(indoc! {"
14111        fn a() {
14112            «// a();
14113            /// b();
14114            //! c();ˇ»
14115        }
14116    "});
14117
14118    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14119
14120    cx.assert_editor_state(indoc! {"
14121        fn a() {
14122            «a();
14123            b();
14124            c();ˇ»
14125        }
14126    "});
14127}
14128
14129#[gpui::test]
14130async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14131    init_test(cx, |_| {});
14132    let mut cx = EditorTestContext::new(cx).await;
14133    let language = Arc::new(Language::new(
14134        LanguageConfig {
14135            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14136            ..Default::default()
14137        },
14138        Some(tree_sitter_rust::LANGUAGE.into()),
14139    ));
14140    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14141
14142    let toggle_comments = &ToggleComments {
14143        advance_downwards: false,
14144        ignore_indent: true,
14145    };
14146
14147    // If multiple selections intersect a line, the line is only toggled once.
14148    cx.set_state(indoc! {"
14149        fn a() {
14150        //    «b();
14151        //    c();
14152        //    ˇ» d();
14153        }
14154    "});
14155
14156    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14157
14158    cx.assert_editor_state(indoc! {"
14159        fn a() {
14160            «b();
14161            c();
14162            ˇ» d();
14163        }
14164    "});
14165
14166    // The comment prefix is inserted at the beginning of each line
14167    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14168
14169    cx.assert_editor_state(indoc! {"
14170        fn a() {
14171        //    «b();
14172        //    c();
14173        //    ˇ» d();
14174        }
14175    "});
14176
14177    // If a selection ends at the beginning of a line, that line is not toggled.
14178    cx.set_selections_state(indoc! {"
14179        fn a() {
14180        //    b();
14181        //    «c();
14182        ˇ»//     d();
14183        }
14184    "});
14185
14186    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14187
14188    cx.assert_editor_state(indoc! {"
14189        fn a() {
14190        //    b();
14191            «c();
14192        ˇ»//     d();
14193        }
14194    "});
14195
14196    // If a selection span a single line and is empty, the line is toggled.
14197    cx.set_state(indoc! {"
14198        fn a() {
14199            a();
14200            b();
14201        ˇ
14202        }
14203    "});
14204
14205    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14206
14207    cx.assert_editor_state(indoc! {"
14208        fn a() {
14209            a();
14210            b();
14211        //ˇ
14212        }
14213    "});
14214
14215    // If a selection span multiple lines, empty lines are not toggled.
14216    cx.set_state(indoc! {"
14217        fn a() {
14218            «a();
14219
14220            c();ˇ»
14221        }
14222    "});
14223
14224    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14225
14226    cx.assert_editor_state(indoc! {"
14227        fn a() {
14228        //    «a();
14229
14230        //    c();ˇ»
14231        }
14232    "});
14233
14234    // If a selection includes multiple comment prefixes, all lines are uncommented.
14235    cx.set_state(indoc! {"
14236        fn a() {
14237        //    «a();
14238        ///    b();
14239        //!    c();ˇ»
14240        }
14241    "});
14242
14243    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14244
14245    cx.assert_editor_state(indoc! {"
14246        fn a() {
14247            «a();
14248            b();
14249            c();ˇ»
14250        }
14251    "});
14252}
14253
14254#[gpui::test]
14255async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
14256    init_test(cx, |_| {});
14257
14258    let language = Arc::new(Language::new(
14259        LanguageConfig {
14260            line_comments: vec!["// ".into()],
14261            ..Default::default()
14262        },
14263        Some(tree_sitter_rust::LANGUAGE.into()),
14264    ));
14265
14266    let mut cx = EditorTestContext::new(cx).await;
14267
14268    cx.language_registry().add(language.clone());
14269    cx.update_buffer(|buffer, cx| {
14270        buffer.set_language(Some(language), cx);
14271    });
14272
14273    let toggle_comments = &ToggleComments {
14274        advance_downwards: true,
14275        ignore_indent: false,
14276    };
14277
14278    // Single cursor on one line -> advance
14279    // Cursor moves horizontally 3 characters as well on non-blank line
14280    cx.set_state(indoc!(
14281        "fn a() {
14282             ˇdog();
14283             cat();
14284        }"
14285    ));
14286    cx.update_editor(|editor, window, cx| {
14287        editor.toggle_comments(toggle_comments, window, cx);
14288    });
14289    cx.assert_editor_state(indoc!(
14290        "fn a() {
14291             // dog();
14292             catˇ();
14293        }"
14294    ));
14295
14296    // Single selection on one line -> don't advance
14297    cx.set_state(indoc!(
14298        "fn a() {
14299             «dog()ˇ»;
14300             cat();
14301        }"
14302    ));
14303    cx.update_editor(|editor, window, cx| {
14304        editor.toggle_comments(toggle_comments, window, cx);
14305    });
14306    cx.assert_editor_state(indoc!(
14307        "fn a() {
14308             // «dog()ˇ»;
14309             cat();
14310        }"
14311    ));
14312
14313    // Multiple cursors on one line -> advance
14314    cx.set_state(indoc!(
14315        "fn a() {
14316             ˇdˇog();
14317             cat();
14318        }"
14319    ));
14320    cx.update_editor(|editor, window, cx| {
14321        editor.toggle_comments(toggle_comments, window, cx);
14322    });
14323    cx.assert_editor_state(indoc!(
14324        "fn a() {
14325             // dog();
14326             catˇ(ˇ);
14327        }"
14328    ));
14329
14330    // Multiple cursors on one line, with selection -> don't advance
14331    cx.set_state(indoc!(
14332        "fn a() {
14333             ˇdˇog«()ˇ»;
14334             cat();
14335        }"
14336    ));
14337    cx.update_editor(|editor, window, cx| {
14338        editor.toggle_comments(toggle_comments, window, cx);
14339    });
14340    cx.assert_editor_state(indoc!(
14341        "fn a() {
14342             // ˇdˇog«()ˇ»;
14343             cat();
14344        }"
14345    ));
14346
14347    // Single cursor on one line -> advance
14348    // Cursor moves to column 0 on blank line
14349    cx.set_state(indoc!(
14350        "fn a() {
14351             ˇdog();
14352
14353             cat();
14354        }"
14355    ));
14356    cx.update_editor(|editor, window, cx| {
14357        editor.toggle_comments(toggle_comments, window, cx);
14358    });
14359    cx.assert_editor_state(indoc!(
14360        "fn a() {
14361             // dog();
14362        ˇ
14363             cat();
14364        }"
14365    ));
14366
14367    // Single cursor on one line -> advance
14368    // Cursor starts and ends at column 0
14369    cx.set_state(indoc!(
14370        "fn a() {
14371         ˇ    dog();
14372             cat();
14373        }"
14374    ));
14375    cx.update_editor(|editor, window, cx| {
14376        editor.toggle_comments(toggle_comments, window, cx);
14377    });
14378    cx.assert_editor_state(indoc!(
14379        "fn a() {
14380             // dog();
14381         ˇ    cat();
14382        }"
14383    ));
14384}
14385
14386#[gpui::test]
14387async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14388    init_test(cx, |_| {});
14389
14390    let mut cx = EditorTestContext::new(cx).await;
14391
14392    let html_language = Arc::new(
14393        Language::new(
14394            LanguageConfig {
14395                name: "HTML".into(),
14396                block_comment: Some(BlockCommentConfig {
14397                    start: "<!-- ".into(),
14398                    prefix: "".into(),
14399                    end: " -->".into(),
14400                    tab_size: 0,
14401                }),
14402                ..Default::default()
14403            },
14404            Some(tree_sitter_html::LANGUAGE.into()),
14405        )
14406        .with_injection_query(
14407            r#"
14408            (script_element
14409                (raw_text) @injection.content
14410                (#set! injection.language "javascript"))
14411            "#,
14412        )
14413        .unwrap(),
14414    );
14415
14416    let javascript_language = Arc::new(Language::new(
14417        LanguageConfig {
14418            name: "JavaScript".into(),
14419            line_comments: vec!["// ".into()],
14420            ..Default::default()
14421        },
14422        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14423    ));
14424
14425    cx.language_registry().add(html_language.clone());
14426    cx.language_registry().add(javascript_language);
14427    cx.update_buffer(|buffer, cx| {
14428        buffer.set_language(Some(html_language), cx);
14429    });
14430
14431    // Toggle comments for empty selections
14432    cx.set_state(
14433        &r#"
14434            <p>A</p>ˇ
14435            <p>B</p>ˇ
14436            <p>C</p>ˇ
14437        "#
14438        .unindent(),
14439    );
14440    cx.update_editor(|editor, window, cx| {
14441        editor.toggle_comments(&ToggleComments::default(), window, cx)
14442    });
14443    cx.assert_editor_state(
14444        &r#"
14445            <!-- <p>A</p>ˇ -->
14446            <!-- <p>B</p>ˇ -->
14447            <!-- <p>C</p>ˇ -->
14448        "#
14449        .unindent(),
14450    );
14451    cx.update_editor(|editor, window, cx| {
14452        editor.toggle_comments(&ToggleComments::default(), window, cx)
14453    });
14454    cx.assert_editor_state(
14455        &r#"
14456            <p>A</p>ˇ
14457            <p>B</p>ˇ
14458            <p>C</p>ˇ
14459        "#
14460        .unindent(),
14461    );
14462
14463    // Toggle comments for mixture of empty and non-empty selections, where
14464    // multiple selections occupy a given line.
14465    cx.set_state(
14466        &r#"
14467            <p>A«</p>
14468            <p>ˇ»B</p>ˇ
14469            <p>C«</p>
14470            <p>ˇ»D</p>ˇ
14471        "#
14472        .unindent(),
14473    );
14474
14475    cx.update_editor(|editor, window, cx| {
14476        editor.toggle_comments(&ToggleComments::default(), window, cx)
14477    });
14478    cx.assert_editor_state(
14479        &r#"
14480            <!-- <p>A«</p>
14481            <p>ˇ»B</p>ˇ -->
14482            <!-- <p>C«</p>
14483            <p>ˇ»D</p>ˇ -->
14484        "#
14485        .unindent(),
14486    );
14487    cx.update_editor(|editor, window, cx| {
14488        editor.toggle_comments(&ToggleComments::default(), window, cx)
14489    });
14490    cx.assert_editor_state(
14491        &r#"
14492            <p>A«</p>
14493            <p>ˇ»B</p>ˇ
14494            <p>C«</p>
14495            <p>ˇ»D</p>ˇ
14496        "#
14497        .unindent(),
14498    );
14499
14500    // Toggle comments when different languages are active for different
14501    // selections.
14502    cx.set_state(
14503        &r#"
14504            ˇ<script>
14505                ˇvar x = new Y();
14506            ˇ</script>
14507        "#
14508        .unindent(),
14509    );
14510    cx.executor().run_until_parked();
14511    cx.update_editor(|editor, window, cx| {
14512        editor.toggle_comments(&ToggleComments::default(), window, cx)
14513    });
14514    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14515    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14516    cx.assert_editor_state(
14517        &r#"
14518            <!-- ˇ<script> -->
14519                // ˇvar x = new Y();
14520            <!-- ˇ</script> -->
14521        "#
14522        .unindent(),
14523    );
14524}
14525
14526#[gpui::test]
14527fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14528    init_test(cx, |_| {});
14529
14530    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14531    let multibuffer = cx.new(|cx| {
14532        let mut multibuffer = MultiBuffer::new(ReadWrite);
14533        multibuffer.push_excerpts(
14534            buffer.clone(),
14535            [
14536                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14537                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14538            ],
14539            cx,
14540        );
14541        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14542        multibuffer
14543    });
14544
14545    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14546    editor.update_in(cx, |editor, window, cx| {
14547        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14549            s.select_ranges([
14550                Point::new(0, 0)..Point::new(0, 0),
14551                Point::new(1, 0)..Point::new(1, 0),
14552            ])
14553        });
14554
14555        editor.handle_input("X", window, cx);
14556        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14557        assert_eq!(
14558            editor.selections.ranges(cx),
14559            [
14560                Point::new(0, 1)..Point::new(0, 1),
14561                Point::new(1, 1)..Point::new(1, 1),
14562            ]
14563        );
14564
14565        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14566        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14567            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14568        });
14569        editor.backspace(&Default::default(), window, cx);
14570        assert_eq!(editor.text(cx), "Xa\nbbb");
14571        assert_eq!(
14572            editor.selections.ranges(cx),
14573            [Point::new(1, 0)..Point::new(1, 0)]
14574        );
14575
14576        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14577            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14578        });
14579        editor.backspace(&Default::default(), window, cx);
14580        assert_eq!(editor.text(cx), "X\nbb");
14581        assert_eq!(
14582            editor.selections.ranges(cx),
14583            [Point::new(0, 1)..Point::new(0, 1)]
14584        );
14585    });
14586}
14587
14588#[gpui::test]
14589fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14590    init_test(cx, |_| {});
14591
14592    let markers = vec![('[', ']').into(), ('(', ')').into()];
14593    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14594        indoc! {"
14595            [aaaa
14596            (bbbb]
14597            cccc)",
14598        },
14599        markers.clone(),
14600    );
14601    let excerpt_ranges = markers.into_iter().map(|marker| {
14602        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14603        ExcerptRange::new(context)
14604    });
14605    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14606    let multibuffer = cx.new(|cx| {
14607        let mut multibuffer = MultiBuffer::new(ReadWrite);
14608        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14609        multibuffer
14610    });
14611
14612    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14613    editor.update_in(cx, |editor, window, cx| {
14614        let (expected_text, selection_ranges) = marked_text_ranges(
14615            indoc! {"
14616                aaaa
14617                bˇbbb
14618                bˇbbˇb
14619                cccc"
14620            },
14621            true,
14622        );
14623        assert_eq!(editor.text(cx), expected_text);
14624        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14625            s.select_ranges(selection_ranges)
14626        });
14627
14628        editor.handle_input("X", window, cx);
14629
14630        let (expected_text, expected_selections) = marked_text_ranges(
14631            indoc! {"
14632                aaaa
14633                bXˇbbXb
14634                bXˇbbXˇb
14635                cccc"
14636            },
14637            false,
14638        );
14639        assert_eq!(editor.text(cx), expected_text);
14640        assert_eq!(editor.selections.ranges(cx), expected_selections);
14641
14642        editor.newline(&Newline, window, cx);
14643        let (expected_text, expected_selections) = marked_text_ranges(
14644            indoc! {"
14645                aaaa
14646                bX
14647                ˇbbX
14648                b
14649                bX
14650                ˇbbX
14651                ˇb
14652                cccc"
14653            },
14654            false,
14655        );
14656        assert_eq!(editor.text(cx), expected_text);
14657        assert_eq!(editor.selections.ranges(cx), expected_selections);
14658    });
14659}
14660
14661#[gpui::test]
14662fn test_refresh_selections(cx: &mut TestAppContext) {
14663    init_test(cx, |_| {});
14664
14665    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14666    let mut excerpt1_id = None;
14667    let multibuffer = cx.new(|cx| {
14668        let mut multibuffer = MultiBuffer::new(ReadWrite);
14669        excerpt1_id = multibuffer
14670            .push_excerpts(
14671                buffer.clone(),
14672                [
14673                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14674                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14675                ],
14676                cx,
14677            )
14678            .into_iter()
14679            .next();
14680        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14681        multibuffer
14682    });
14683
14684    let editor = cx.add_window(|window, cx| {
14685        let mut editor = build_editor(multibuffer.clone(), window, cx);
14686        let snapshot = editor.snapshot(window, cx);
14687        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14688            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14689        });
14690        editor.begin_selection(
14691            Point::new(2, 1).to_display_point(&snapshot),
14692            true,
14693            1,
14694            window,
14695            cx,
14696        );
14697        assert_eq!(
14698            editor.selections.ranges(cx),
14699            [
14700                Point::new(1, 3)..Point::new(1, 3),
14701                Point::new(2, 1)..Point::new(2, 1),
14702            ]
14703        );
14704        editor
14705    });
14706
14707    // Refreshing selections is a no-op when excerpts haven't changed.
14708    _ = editor.update(cx, |editor, window, cx| {
14709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14710        assert_eq!(
14711            editor.selections.ranges(cx),
14712            [
14713                Point::new(1, 3)..Point::new(1, 3),
14714                Point::new(2, 1)..Point::new(2, 1),
14715            ]
14716        );
14717    });
14718
14719    multibuffer.update(cx, |multibuffer, cx| {
14720        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14721    });
14722    _ = editor.update(cx, |editor, window, cx| {
14723        // Removing an excerpt causes the first selection to become degenerate.
14724        assert_eq!(
14725            editor.selections.ranges(cx),
14726            [
14727                Point::new(0, 0)..Point::new(0, 0),
14728                Point::new(0, 1)..Point::new(0, 1)
14729            ]
14730        );
14731
14732        // Refreshing selections will relocate the first selection to the original buffer
14733        // location.
14734        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14735        assert_eq!(
14736            editor.selections.ranges(cx),
14737            [
14738                Point::new(0, 1)..Point::new(0, 1),
14739                Point::new(0, 3)..Point::new(0, 3)
14740            ]
14741        );
14742        assert!(editor.selections.pending_anchor().is_some());
14743    });
14744}
14745
14746#[gpui::test]
14747fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14748    init_test(cx, |_| {});
14749
14750    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14751    let mut excerpt1_id = None;
14752    let multibuffer = cx.new(|cx| {
14753        let mut multibuffer = MultiBuffer::new(ReadWrite);
14754        excerpt1_id = multibuffer
14755            .push_excerpts(
14756                buffer.clone(),
14757                [
14758                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14759                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14760                ],
14761                cx,
14762            )
14763            .into_iter()
14764            .next();
14765        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14766        multibuffer
14767    });
14768
14769    let editor = cx.add_window(|window, cx| {
14770        let mut editor = build_editor(multibuffer.clone(), window, cx);
14771        let snapshot = editor.snapshot(window, cx);
14772        editor.begin_selection(
14773            Point::new(1, 3).to_display_point(&snapshot),
14774            false,
14775            1,
14776            window,
14777            cx,
14778        );
14779        assert_eq!(
14780            editor.selections.ranges(cx),
14781            [Point::new(1, 3)..Point::new(1, 3)]
14782        );
14783        editor
14784    });
14785
14786    multibuffer.update(cx, |multibuffer, cx| {
14787        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14788    });
14789    _ = editor.update(cx, |editor, window, cx| {
14790        assert_eq!(
14791            editor.selections.ranges(cx),
14792            [Point::new(0, 0)..Point::new(0, 0)]
14793        );
14794
14795        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14796        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14797        assert_eq!(
14798            editor.selections.ranges(cx),
14799            [Point::new(0, 3)..Point::new(0, 3)]
14800        );
14801        assert!(editor.selections.pending_anchor().is_some());
14802    });
14803}
14804
14805#[gpui::test]
14806async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14807    init_test(cx, |_| {});
14808
14809    let language = Arc::new(
14810        Language::new(
14811            LanguageConfig {
14812                brackets: BracketPairConfig {
14813                    pairs: vec![
14814                        BracketPair {
14815                            start: "{".to_string(),
14816                            end: "}".to_string(),
14817                            close: true,
14818                            surround: true,
14819                            newline: true,
14820                        },
14821                        BracketPair {
14822                            start: "/* ".to_string(),
14823                            end: " */".to_string(),
14824                            close: true,
14825                            surround: true,
14826                            newline: true,
14827                        },
14828                    ],
14829                    ..Default::default()
14830                },
14831                ..Default::default()
14832            },
14833            Some(tree_sitter_rust::LANGUAGE.into()),
14834        )
14835        .with_indents_query("")
14836        .unwrap(),
14837    );
14838
14839    let text = concat!(
14840        "{   }\n",     //
14841        "  x\n",       //
14842        "  /*   */\n", //
14843        "x\n",         //
14844        "{{} }\n",     //
14845    );
14846
14847    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14848    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14849    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14850    editor
14851        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14852        .await;
14853
14854    editor.update_in(cx, |editor, window, cx| {
14855        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14856            s.select_display_ranges([
14857                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14858                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14859                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14860            ])
14861        });
14862        editor.newline(&Newline, window, cx);
14863
14864        assert_eq!(
14865            editor.buffer().read(cx).read(cx).text(),
14866            concat!(
14867                "{ \n",    // Suppress rustfmt
14868                "\n",      //
14869                "}\n",     //
14870                "  x\n",   //
14871                "  /* \n", //
14872                "  \n",    //
14873                "  */\n",  //
14874                "x\n",     //
14875                "{{} \n",  //
14876                "}\n",     //
14877            )
14878        );
14879    });
14880}
14881
14882#[gpui::test]
14883fn test_highlighted_ranges(cx: &mut TestAppContext) {
14884    init_test(cx, |_| {});
14885
14886    let editor = cx.add_window(|window, cx| {
14887        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
14888        build_editor(buffer, window, cx)
14889    });
14890
14891    _ = editor.update(cx, |editor, window, cx| {
14892        struct Type1;
14893        struct Type2;
14894
14895        let buffer = editor.buffer.read(cx).snapshot(cx);
14896
14897        let anchor_range =
14898            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
14899
14900        editor.highlight_background::<Type1>(
14901            &[
14902                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
14903                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
14904                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
14905                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
14906            ],
14907            |_| Hsla::red(),
14908            cx,
14909        );
14910        editor.highlight_background::<Type2>(
14911            &[
14912                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
14913                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
14914                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
14915                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
14916            ],
14917            |_| Hsla::green(),
14918            cx,
14919        );
14920
14921        let snapshot = editor.snapshot(window, cx);
14922        let mut highlighted_ranges = editor.background_highlights_in_range(
14923            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
14924            &snapshot,
14925            cx.theme(),
14926        );
14927        // Enforce a consistent ordering based on color without relying on the ordering of the
14928        // highlight's `TypeId` which is non-executor.
14929        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
14930        assert_eq!(
14931            highlighted_ranges,
14932            &[
14933                (
14934                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
14935                    Hsla::red(),
14936                ),
14937                (
14938                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14939                    Hsla::red(),
14940                ),
14941                (
14942                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
14943                    Hsla::green(),
14944                ),
14945                (
14946                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
14947                    Hsla::green(),
14948                ),
14949            ]
14950        );
14951        assert_eq!(
14952            editor.background_highlights_in_range(
14953                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
14954                &snapshot,
14955                cx.theme(),
14956            ),
14957            &[(
14958                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
14959                Hsla::red(),
14960            )]
14961        );
14962    });
14963}
14964
14965#[gpui::test]
14966async fn test_following(cx: &mut TestAppContext) {
14967    init_test(cx, |_| {});
14968
14969    let fs = FakeFs::new(cx.executor());
14970    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
14971
14972    let buffer = project.update(cx, |project, cx| {
14973        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
14974        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
14975    });
14976    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
14977    let follower = cx.update(|cx| {
14978        cx.open_window(
14979            WindowOptions {
14980                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
14981                    gpui::Point::new(px(0.), px(0.)),
14982                    gpui::Point::new(px(10.), px(80.)),
14983                ))),
14984                ..Default::default()
14985            },
14986            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
14987        )
14988        .unwrap()
14989    });
14990
14991    let is_still_following = Rc::new(RefCell::new(true));
14992    let follower_edit_event_count = Rc::new(RefCell::new(0));
14993    let pending_update = Rc::new(RefCell::new(None));
14994    let leader_entity = leader.root(cx).unwrap();
14995    let follower_entity = follower.root(cx).unwrap();
14996    _ = follower.update(cx, {
14997        let update = pending_update.clone();
14998        let is_still_following = is_still_following.clone();
14999        let follower_edit_event_count = follower_edit_event_count.clone();
15000        |_, window, cx| {
15001            cx.subscribe_in(
15002                &leader_entity,
15003                window,
15004                move |_, leader, event, window, cx| {
15005                    leader.read(cx).add_event_to_update_proto(
15006                        event,
15007                        &mut update.borrow_mut(),
15008                        window,
15009                        cx,
15010                    );
15011                },
15012            )
15013            .detach();
15014
15015            cx.subscribe_in(
15016                &follower_entity,
15017                window,
15018                move |_, _, event: &EditorEvent, _window, _cx| {
15019                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15020                        *is_still_following.borrow_mut() = false;
15021                    }
15022
15023                    if let EditorEvent::BufferEdited = event {
15024                        *follower_edit_event_count.borrow_mut() += 1;
15025                    }
15026                },
15027            )
15028            .detach();
15029        }
15030    });
15031
15032    // Update the selections only
15033    _ = leader.update(cx, |leader, window, cx| {
15034        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15035            s.select_ranges([1..1])
15036        });
15037    });
15038    follower
15039        .update(cx, |follower, window, cx| {
15040            follower.apply_update_proto(
15041                &project,
15042                pending_update.borrow_mut().take().unwrap(),
15043                window,
15044                cx,
15045            )
15046        })
15047        .unwrap()
15048        .await
15049        .unwrap();
15050    _ = follower.update(cx, |follower, _, cx| {
15051        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15052    });
15053    assert!(*is_still_following.borrow());
15054    assert_eq!(*follower_edit_event_count.borrow(), 0);
15055
15056    // Update the scroll position only
15057    _ = leader.update(cx, |leader, window, cx| {
15058        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15059    });
15060    follower
15061        .update(cx, |follower, window, cx| {
15062            follower.apply_update_proto(
15063                &project,
15064                pending_update.borrow_mut().take().unwrap(),
15065                window,
15066                cx,
15067            )
15068        })
15069        .unwrap()
15070        .await
15071        .unwrap();
15072    assert_eq!(
15073        follower
15074            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15075            .unwrap(),
15076        gpui::Point::new(1.5, 3.5)
15077    );
15078    assert!(*is_still_following.borrow());
15079    assert_eq!(*follower_edit_event_count.borrow(), 0);
15080
15081    // Update the selections and scroll position. The follower's scroll position is updated
15082    // via autoscroll, not via the leader's exact scroll position.
15083    _ = leader.update(cx, |leader, window, cx| {
15084        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15085            s.select_ranges([0..0])
15086        });
15087        leader.request_autoscroll(Autoscroll::newest(), cx);
15088        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15089    });
15090    follower
15091        .update(cx, |follower, window, cx| {
15092            follower.apply_update_proto(
15093                &project,
15094                pending_update.borrow_mut().take().unwrap(),
15095                window,
15096                cx,
15097            )
15098        })
15099        .unwrap()
15100        .await
15101        .unwrap();
15102    _ = follower.update(cx, |follower, _, cx| {
15103        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15104        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15105    });
15106    assert!(*is_still_following.borrow());
15107
15108    // Creating a pending selection that precedes another selection
15109    _ = leader.update(cx, |leader, window, cx| {
15110        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15111            s.select_ranges([1..1])
15112        });
15113        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15114    });
15115    follower
15116        .update(cx, |follower, window, cx| {
15117            follower.apply_update_proto(
15118                &project,
15119                pending_update.borrow_mut().take().unwrap(),
15120                window,
15121                cx,
15122            )
15123        })
15124        .unwrap()
15125        .await
15126        .unwrap();
15127    _ = follower.update(cx, |follower, _, cx| {
15128        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15129    });
15130    assert!(*is_still_following.borrow());
15131
15132    // Extend the pending selection so that it surrounds another selection
15133    _ = leader.update(cx, |leader, window, cx| {
15134        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
15135    });
15136    follower
15137        .update(cx, |follower, window, cx| {
15138            follower.apply_update_proto(
15139                &project,
15140                pending_update.borrow_mut().take().unwrap(),
15141                window,
15142                cx,
15143            )
15144        })
15145        .unwrap()
15146        .await
15147        .unwrap();
15148    _ = follower.update(cx, |follower, _, cx| {
15149        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
15150    });
15151
15152    // Scrolling locally breaks the follow
15153    _ = follower.update(cx, |follower, window, cx| {
15154        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
15155        follower.set_scroll_anchor(
15156            ScrollAnchor {
15157                anchor: top_anchor,
15158                offset: gpui::Point::new(0.0, 0.5),
15159            },
15160            window,
15161            cx,
15162        );
15163    });
15164    assert!(!(*is_still_following.borrow()));
15165}
15166
15167#[gpui::test]
15168async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
15169    init_test(cx, |_| {});
15170
15171    let fs = FakeFs::new(cx.executor());
15172    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15173    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15174    let pane = workspace
15175        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15176        .unwrap();
15177
15178    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15179
15180    let leader = pane.update_in(cx, |_, window, cx| {
15181        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
15182        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
15183    });
15184
15185    // Start following the editor when it has no excerpts.
15186    let mut state_message =
15187        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15188    let workspace_entity = workspace.root(cx).unwrap();
15189    let follower_1 = cx
15190        .update_window(*workspace.deref(), |_, window, cx| {
15191            Editor::from_state_proto(
15192                workspace_entity,
15193                ViewId {
15194                    creator: CollaboratorId::PeerId(PeerId::default()),
15195                    id: 0,
15196                },
15197                &mut state_message,
15198                window,
15199                cx,
15200            )
15201        })
15202        .unwrap()
15203        .unwrap()
15204        .await
15205        .unwrap();
15206
15207    let update_message = Rc::new(RefCell::new(None));
15208    follower_1.update_in(cx, {
15209        let update = update_message.clone();
15210        |_, window, cx| {
15211            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
15212                leader.read(cx).add_event_to_update_proto(
15213                    event,
15214                    &mut update.borrow_mut(),
15215                    window,
15216                    cx,
15217                );
15218            })
15219            .detach();
15220        }
15221    });
15222
15223    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
15224        (
15225            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
15226            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
15227        )
15228    });
15229
15230    // Insert some excerpts.
15231    leader.update(cx, |leader, cx| {
15232        leader.buffer.update(cx, |multibuffer, cx| {
15233            multibuffer.set_excerpts_for_path(
15234                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
15235                buffer_1.clone(),
15236                vec![
15237                    Point::row_range(0..3),
15238                    Point::row_range(1..6),
15239                    Point::row_range(12..15),
15240                ],
15241                0,
15242                cx,
15243            );
15244            multibuffer.set_excerpts_for_path(
15245                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
15246                buffer_2.clone(),
15247                vec![Point::row_range(0..6), Point::row_range(8..12)],
15248                0,
15249                cx,
15250            );
15251        });
15252    });
15253
15254    // Apply the update of adding the excerpts.
15255    follower_1
15256        .update_in(cx, |follower, window, cx| {
15257            follower.apply_update_proto(
15258                &project,
15259                update_message.borrow().clone().unwrap(),
15260                window,
15261                cx,
15262            )
15263        })
15264        .await
15265        .unwrap();
15266    assert_eq!(
15267        follower_1.update(cx, |editor, cx| editor.text(cx)),
15268        leader.update(cx, |editor, cx| editor.text(cx))
15269    );
15270    update_message.borrow_mut().take();
15271
15272    // Start following separately after it already has excerpts.
15273    let mut state_message =
15274        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15275    let workspace_entity = workspace.root(cx).unwrap();
15276    let follower_2 = cx
15277        .update_window(*workspace.deref(), |_, window, cx| {
15278            Editor::from_state_proto(
15279                workspace_entity,
15280                ViewId {
15281                    creator: CollaboratorId::PeerId(PeerId::default()),
15282                    id: 0,
15283                },
15284                &mut state_message,
15285                window,
15286                cx,
15287            )
15288        })
15289        .unwrap()
15290        .unwrap()
15291        .await
15292        .unwrap();
15293    assert_eq!(
15294        follower_2.update(cx, |editor, cx| editor.text(cx)),
15295        leader.update(cx, |editor, cx| editor.text(cx))
15296    );
15297
15298    // Remove some excerpts.
15299    leader.update(cx, |leader, cx| {
15300        leader.buffer.update(cx, |multibuffer, cx| {
15301            let excerpt_ids = multibuffer.excerpt_ids();
15302            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15303            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15304        });
15305    });
15306
15307    // Apply the update of removing the excerpts.
15308    follower_1
15309        .update_in(cx, |follower, window, cx| {
15310            follower.apply_update_proto(
15311                &project,
15312                update_message.borrow().clone().unwrap(),
15313                window,
15314                cx,
15315            )
15316        })
15317        .await
15318        .unwrap();
15319    follower_2
15320        .update_in(cx, |follower, window, cx| {
15321            follower.apply_update_proto(
15322                &project,
15323                update_message.borrow().clone().unwrap(),
15324                window,
15325                cx,
15326            )
15327        })
15328        .await
15329        .unwrap();
15330    update_message.borrow_mut().take();
15331    assert_eq!(
15332        follower_1.update(cx, |editor, cx| editor.text(cx)),
15333        leader.update(cx, |editor, cx| editor.text(cx))
15334    );
15335}
15336
15337#[gpui::test]
15338async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15339    init_test(cx, |_| {});
15340
15341    let mut cx = EditorTestContext::new(cx).await;
15342    let lsp_store =
15343        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15344
15345    cx.set_state(indoc! {"
15346        ˇfn func(abc def: i32) -> u32 {
15347        }
15348    "});
15349
15350    cx.update(|_, cx| {
15351        lsp_store.update(cx, |lsp_store, cx| {
15352            lsp_store
15353                .update_diagnostics(
15354                    LanguageServerId(0),
15355                    lsp::PublishDiagnosticsParams {
15356                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
15357                        version: None,
15358                        diagnostics: vec![
15359                            lsp::Diagnostic {
15360                                range: lsp::Range::new(
15361                                    lsp::Position::new(0, 11),
15362                                    lsp::Position::new(0, 12),
15363                                ),
15364                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15365                                ..Default::default()
15366                            },
15367                            lsp::Diagnostic {
15368                                range: lsp::Range::new(
15369                                    lsp::Position::new(0, 12),
15370                                    lsp::Position::new(0, 15),
15371                                ),
15372                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15373                                ..Default::default()
15374                            },
15375                            lsp::Diagnostic {
15376                                range: lsp::Range::new(
15377                                    lsp::Position::new(0, 25),
15378                                    lsp::Position::new(0, 28),
15379                                ),
15380                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15381                                ..Default::default()
15382                            },
15383                        ],
15384                    },
15385                    None,
15386                    DiagnosticSourceKind::Pushed,
15387                    &[],
15388                    cx,
15389                )
15390                .unwrap()
15391        });
15392    });
15393
15394    executor.run_until_parked();
15395
15396    cx.update_editor(|editor, window, cx| {
15397        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15398    });
15399
15400    cx.assert_editor_state(indoc! {"
15401        fn func(abc def: i32) -> ˇu32 {
15402        }
15403    "});
15404
15405    cx.update_editor(|editor, window, cx| {
15406        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15407    });
15408
15409    cx.assert_editor_state(indoc! {"
15410        fn func(abc ˇdef: i32) -> u32 {
15411        }
15412    "});
15413
15414    cx.update_editor(|editor, window, cx| {
15415        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15416    });
15417
15418    cx.assert_editor_state(indoc! {"
15419        fn func(abcˇ def: i32) -> u32 {
15420        }
15421    "});
15422
15423    cx.update_editor(|editor, window, cx| {
15424        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15425    });
15426
15427    cx.assert_editor_state(indoc! {"
15428        fn func(abc def: i32) -> ˇu32 {
15429        }
15430    "});
15431}
15432
15433#[gpui::test]
15434async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15435    init_test(cx, |_| {});
15436
15437    let mut cx = EditorTestContext::new(cx).await;
15438
15439    let diff_base = r#"
15440        use some::mod;
15441
15442        const A: u32 = 42;
15443
15444        fn main() {
15445            println!("hello");
15446
15447            println!("world");
15448        }
15449        "#
15450    .unindent();
15451
15452    // Edits are modified, removed, modified, added
15453    cx.set_state(
15454        &r#"
15455        use some::modified;
15456
15457        ˇ
15458        fn main() {
15459            println!("hello there");
15460
15461            println!("around the");
15462            println!("world");
15463        }
15464        "#
15465        .unindent(),
15466    );
15467
15468    cx.set_head_text(&diff_base);
15469    executor.run_until_parked();
15470
15471    cx.update_editor(|editor, window, cx| {
15472        //Wrap around the bottom of the buffer
15473        for _ in 0..3 {
15474            editor.go_to_next_hunk(&GoToHunk, window, cx);
15475        }
15476    });
15477
15478    cx.assert_editor_state(
15479        &r#"
15480        ˇuse some::modified;
15481
15482
15483        fn main() {
15484            println!("hello there");
15485
15486            println!("around the");
15487            println!("world");
15488        }
15489        "#
15490        .unindent(),
15491    );
15492
15493    cx.update_editor(|editor, window, cx| {
15494        //Wrap around the top of the buffer
15495        for _ in 0..2 {
15496            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15497        }
15498    });
15499
15500    cx.assert_editor_state(
15501        &r#"
15502        use some::modified;
15503
15504
15505        fn main() {
15506        ˇ    println!("hello there");
15507
15508            println!("around the");
15509            println!("world");
15510        }
15511        "#
15512        .unindent(),
15513    );
15514
15515    cx.update_editor(|editor, window, cx| {
15516        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15517    });
15518
15519    cx.assert_editor_state(
15520        &r#"
15521        use some::modified;
15522
15523        ˇ
15524        fn main() {
15525            println!("hello there");
15526
15527            println!("around the");
15528            println!("world");
15529        }
15530        "#
15531        .unindent(),
15532    );
15533
15534    cx.update_editor(|editor, window, cx| {
15535        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15536    });
15537
15538    cx.assert_editor_state(
15539        &r#"
15540        ˇuse some::modified;
15541
15542
15543        fn main() {
15544            println!("hello there");
15545
15546            println!("around the");
15547            println!("world");
15548        }
15549        "#
15550        .unindent(),
15551    );
15552
15553    cx.update_editor(|editor, window, cx| {
15554        for _ in 0..2 {
15555            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15556        }
15557    });
15558
15559    cx.assert_editor_state(
15560        &r#"
15561        use some::modified;
15562
15563
15564        fn main() {
15565        ˇ    println!("hello there");
15566
15567            println!("around the");
15568            println!("world");
15569        }
15570        "#
15571        .unindent(),
15572    );
15573
15574    cx.update_editor(|editor, window, cx| {
15575        editor.fold(&Fold, window, cx);
15576    });
15577
15578    cx.update_editor(|editor, window, cx| {
15579        editor.go_to_next_hunk(&GoToHunk, window, cx);
15580    });
15581
15582    cx.assert_editor_state(
15583        &r#"
15584        ˇuse some::modified;
15585
15586
15587        fn main() {
15588            println!("hello there");
15589
15590            println!("around the");
15591            println!("world");
15592        }
15593        "#
15594        .unindent(),
15595    );
15596}
15597
15598#[test]
15599fn test_split_words() {
15600    fn split(text: &str) -> Vec<&str> {
15601        split_words(text).collect()
15602    }
15603
15604    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15605    assert_eq!(split("hello_world"), &["hello_", "world"]);
15606    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15607    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15608    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15609    assert_eq!(split("helloworld"), &["helloworld"]);
15610
15611    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15612}
15613
15614#[gpui::test]
15615async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15616    init_test(cx, |_| {});
15617
15618    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15619    let mut assert = |before, after| {
15620        let _state_context = cx.set_state(before);
15621        cx.run_until_parked();
15622        cx.update_editor(|editor, window, cx| {
15623            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15624        });
15625        cx.run_until_parked();
15626        cx.assert_editor_state(after);
15627    };
15628
15629    // Outside bracket jumps to outside of matching bracket
15630    assert("console.logˇ(var);", "console.log(var)ˇ;");
15631    assert("console.log(var)ˇ;", "console.logˇ(var);");
15632
15633    // Inside bracket jumps to inside of matching bracket
15634    assert("console.log(ˇvar);", "console.log(varˇ);");
15635    assert("console.log(varˇ);", "console.log(ˇvar);");
15636
15637    // When outside a bracket and inside, favor jumping to the inside bracket
15638    assert(
15639        "console.log('foo', [1, 2, 3]ˇ);",
15640        "console.log(ˇ'foo', [1, 2, 3]);",
15641    );
15642    assert(
15643        "console.log(ˇ'foo', [1, 2, 3]);",
15644        "console.log('foo', [1, 2, 3]ˇ);",
15645    );
15646
15647    // Bias forward if two options are equally likely
15648    assert(
15649        "let result = curried_fun()ˇ();",
15650        "let result = curried_fun()()ˇ;",
15651    );
15652
15653    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15654    assert(
15655        indoc! {"
15656            function test() {
15657                console.log('test')ˇ
15658            }"},
15659        indoc! {"
15660            function test() {
15661                console.logˇ('test')
15662            }"},
15663    );
15664}
15665
15666#[gpui::test]
15667async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15668    init_test(cx, |_| {});
15669
15670    let fs = FakeFs::new(cx.executor());
15671    fs.insert_tree(
15672        path!("/a"),
15673        json!({
15674            "main.rs": "fn main() { let a = 5; }",
15675            "other.rs": "// Test file",
15676        }),
15677    )
15678    .await;
15679    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15680
15681    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15682    language_registry.add(Arc::new(Language::new(
15683        LanguageConfig {
15684            name: "Rust".into(),
15685            matcher: LanguageMatcher {
15686                path_suffixes: vec!["rs".to_string()],
15687                ..Default::default()
15688            },
15689            brackets: BracketPairConfig {
15690                pairs: vec![BracketPair {
15691                    start: "{".to_string(),
15692                    end: "}".to_string(),
15693                    close: true,
15694                    surround: true,
15695                    newline: true,
15696                }],
15697                disabled_scopes_by_bracket_ix: Vec::new(),
15698            },
15699            ..Default::default()
15700        },
15701        Some(tree_sitter_rust::LANGUAGE.into()),
15702    )));
15703    let mut fake_servers = language_registry.register_fake_lsp(
15704        "Rust",
15705        FakeLspAdapter {
15706            capabilities: lsp::ServerCapabilities {
15707                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15708                    first_trigger_character: "{".to_string(),
15709                    more_trigger_character: None,
15710                }),
15711                ..Default::default()
15712            },
15713            ..Default::default()
15714        },
15715    );
15716
15717    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15718
15719    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15720
15721    let worktree_id = workspace
15722        .update(cx, |workspace, _, cx| {
15723            workspace.project().update(cx, |project, cx| {
15724                project.worktrees(cx).next().unwrap().read(cx).id()
15725            })
15726        })
15727        .unwrap();
15728
15729    let buffer = project
15730        .update(cx, |project, cx| {
15731            project.open_local_buffer(path!("/a/main.rs"), cx)
15732        })
15733        .await
15734        .unwrap();
15735    let editor_handle = workspace
15736        .update(cx, |workspace, window, cx| {
15737            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15738        })
15739        .unwrap()
15740        .await
15741        .unwrap()
15742        .downcast::<Editor>()
15743        .unwrap();
15744
15745    cx.executor().start_waiting();
15746    let fake_server = fake_servers.next().await.unwrap();
15747
15748    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15749        |params, _| async move {
15750            assert_eq!(
15751                params.text_document_position.text_document.uri,
15752                lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
15753            );
15754            assert_eq!(
15755                params.text_document_position.position,
15756                lsp::Position::new(0, 21),
15757            );
15758
15759            Ok(Some(vec![lsp::TextEdit {
15760                new_text: "]".to_string(),
15761                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15762            }]))
15763        },
15764    );
15765
15766    editor_handle.update_in(cx, |editor, window, cx| {
15767        window.focus(&editor.focus_handle(cx));
15768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15769            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15770        });
15771        editor.handle_input("{", window, cx);
15772    });
15773
15774    cx.executor().run_until_parked();
15775
15776    buffer.update(cx, |buffer, _| {
15777        assert_eq!(
15778            buffer.text(),
15779            "fn main() { let a = {5}; }",
15780            "No extra braces from on type formatting should appear in the buffer"
15781        )
15782    });
15783}
15784
15785#[gpui::test(iterations = 20, seeds(31))]
15786async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15787    init_test(cx, |_| {});
15788
15789    let mut cx = EditorLspTestContext::new_rust(
15790        lsp::ServerCapabilities {
15791            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15792                first_trigger_character: ".".to_string(),
15793                more_trigger_character: None,
15794            }),
15795            ..Default::default()
15796        },
15797        cx,
15798    )
15799    .await;
15800
15801    cx.update_buffer(|buffer, _| {
15802        // This causes autoindent to be async.
15803        buffer.set_sync_parse_timeout(Duration::ZERO)
15804    });
15805
15806    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15807    cx.simulate_keystroke("\n");
15808    cx.run_until_parked();
15809
15810    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
15811    let mut request =
15812        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15813            let buffer_cloned = buffer_cloned.clone();
15814            async move {
15815                buffer_cloned.update(&mut cx, |buffer, _| {
15816                    assert_eq!(
15817                        buffer.text(),
15818                        "fn c() {\n    d()\n        .\n}\n",
15819                        "OnTypeFormatting should triggered after autoindent applied"
15820                    )
15821                })?;
15822
15823                Ok(Some(vec![]))
15824            }
15825        });
15826
15827    cx.simulate_keystroke(".");
15828    cx.run_until_parked();
15829
15830    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15831    assert!(request.next().await.is_some());
15832    request.close();
15833    assert!(request.next().await.is_none());
15834}
15835
15836#[gpui::test]
15837async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15838    init_test(cx, |_| {});
15839
15840    let fs = FakeFs::new(cx.executor());
15841    fs.insert_tree(
15842        path!("/a"),
15843        json!({
15844            "main.rs": "fn main() { let a = 5; }",
15845            "other.rs": "// Test file",
15846        }),
15847    )
15848    .await;
15849
15850    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15851
15852    let server_restarts = Arc::new(AtomicUsize::new(0));
15853    let closure_restarts = Arc::clone(&server_restarts);
15854    let language_server_name = "test language server";
15855    let language_name: LanguageName = "Rust".into();
15856
15857    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15858    language_registry.add(Arc::new(Language::new(
15859        LanguageConfig {
15860            name: language_name.clone(),
15861            matcher: LanguageMatcher {
15862                path_suffixes: vec!["rs".to_string()],
15863                ..Default::default()
15864            },
15865            ..Default::default()
15866        },
15867        Some(tree_sitter_rust::LANGUAGE.into()),
15868    )));
15869    let mut fake_servers = language_registry.register_fake_lsp(
15870        "Rust",
15871        FakeLspAdapter {
15872            name: language_server_name,
15873            initialization_options: Some(json!({
15874                "testOptionValue": true
15875            })),
15876            initializer: Some(Box::new(move |fake_server| {
15877                let task_restarts = Arc::clone(&closure_restarts);
15878                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
15879                    task_restarts.fetch_add(1, atomic::Ordering::Release);
15880                    futures::future::ready(Ok(()))
15881                });
15882            })),
15883            ..Default::default()
15884        },
15885    );
15886
15887    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15888    let _buffer = project
15889        .update(cx, |project, cx| {
15890            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
15891        })
15892        .await
15893        .unwrap();
15894    let _fake_server = fake_servers.next().await.unwrap();
15895    update_test_language_settings(cx, |language_settings| {
15896        language_settings.languages.0.insert(
15897            language_name.clone(),
15898            LanguageSettingsContent {
15899                tab_size: NonZeroU32::new(8),
15900                ..Default::default()
15901            },
15902        );
15903    });
15904    cx.executor().run_until_parked();
15905    assert_eq!(
15906        server_restarts.load(atomic::Ordering::Acquire),
15907        0,
15908        "Should not restart LSP server on an unrelated change"
15909    );
15910
15911    update_test_project_settings(cx, |project_settings| {
15912        project_settings.lsp.insert(
15913            "Some other server name".into(),
15914            LspSettings {
15915                binary: None,
15916                settings: None,
15917                initialization_options: Some(json!({
15918                    "some other init value": false
15919                })),
15920                enable_lsp_tasks: false,
15921            },
15922        );
15923    });
15924    cx.executor().run_until_parked();
15925    assert_eq!(
15926        server_restarts.load(atomic::Ordering::Acquire),
15927        0,
15928        "Should not restart LSP server on an unrelated LSP settings change"
15929    );
15930
15931    update_test_project_settings(cx, |project_settings| {
15932        project_settings.lsp.insert(
15933            language_server_name.into(),
15934            LspSettings {
15935                binary: None,
15936                settings: None,
15937                initialization_options: Some(json!({
15938                    "anotherInitValue": false
15939                })),
15940                enable_lsp_tasks: false,
15941            },
15942        );
15943    });
15944    cx.executor().run_until_parked();
15945    assert_eq!(
15946        server_restarts.load(atomic::Ordering::Acquire),
15947        1,
15948        "Should restart LSP server on a related LSP settings change"
15949    );
15950
15951    update_test_project_settings(cx, |project_settings| {
15952        project_settings.lsp.insert(
15953            language_server_name.into(),
15954            LspSettings {
15955                binary: None,
15956                settings: None,
15957                initialization_options: Some(json!({
15958                    "anotherInitValue": false
15959                })),
15960                enable_lsp_tasks: false,
15961            },
15962        );
15963    });
15964    cx.executor().run_until_parked();
15965    assert_eq!(
15966        server_restarts.load(atomic::Ordering::Acquire),
15967        1,
15968        "Should not restart LSP server on a related LSP settings change that is the same"
15969    );
15970
15971    update_test_project_settings(cx, |project_settings| {
15972        project_settings.lsp.insert(
15973            language_server_name.into(),
15974            LspSettings {
15975                binary: None,
15976                settings: None,
15977                initialization_options: None,
15978                enable_lsp_tasks: false,
15979            },
15980        );
15981    });
15982    cx.executor().run_until_parked();
15983    assert_eq!(
15984        server_restarts.load(atomic::Ordering::Acquire),
15985        2,
15986        "Should restart LSP server on another related LSP settings change"
15987    );
15988}
15989
15990#[gpui::test]
15991async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
15992    init_test(cx, |_| {});
15993
15994    let mut cx = EditorLspTestContext::new_rust(
15995        lsp::ServerCapabilities {
15996            completion_provider: Some(lsp::CompletionOptions {
15997                trigger_characters: Some(vec![".".to_string()]),
15998                resolve_provider: Some(true),
15999                ..Default::default()
16000            }),
16001            ..Default::default()
16002        },
16003        cx,
16004    )
16005    .await;
16006
16007    cx.set_state("fn main() { let a = 2ˇ; }");
16008    cx.simulate_keystroke(".");
16009    let completion_item = lsp::CompletionItem {
16010        label: "some".into(),
16011        kind: Some(lsp::CompletionItemKind::SNIPPET),
16012        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16013        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16014            kind: lsp::MarkupKind::Markdown,
16015            value: "```rust\nSome(2)\n```".to_string(),
16016        })),
16017        deprecated: Some(false),
16018        sort_text: Some("fffffff2".to_string()),
16019        filter_text: Some("some".to_string()),
16020        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16021        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16022            range: lsp::Range {
16023                start: lsp::Position {
16024                    line: 0,
16025                    character: 22,
16026                },
16027                end: lsp::Position {
16028                    line: 0,
16029                    character: 22,
16030                },
16031            },
16032            new_text: "Some(2)".to_string(),
16033        })),
16034        additional_text_edits: Some(vec![lsp::TextEdit {
16035            range: lsp::Range {
16036                start: lsp::Position {
16037                    line: 0,
16038                    character: 20,
16039                },
16040                end: lsp::Position {
16041                    line: 0,
16042                    character: 22,
16043                },
16044            },
16045            new_text: "".to_string(),
16046        }]),
16047        ..Default::default()
16048    };
16049
16050    let closure_completion_item = completion_item.clone();
16051    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16052        let task_completion_item = closure_completion_item.clone();
16053        async move {
16054            Ok(Some(lsp::CompletionResponse::Array(vec![
16055                task_completion_item,
16056            ])))
16057        }
16058    });
16059
16060    request.next().await;
16061
16062    cx.condition(|editor, _| editor.context_menu_visible())
16063        .await;
16064    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16065        editor
16066            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16067            .unwrap()
16068    });
16069    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16070
16071    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16072        let task_completion_item = completion_item.clone();
16073        async move { Ok(task_completion_item) }
16074    })
16075    .next()
16076    .await
16077    .unwrap();
16078    apply_additional_edits.await.unwrap();
16079    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16080}
16081
16082#[gpui::test]
16083async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16084    init_test(cx, |_| {});
16085
16086    let mut cx = EditorLspTestContext::new_rust(
16087        lsp::ServerCapabilities {
16088            completion_provider: Some(lsp::CompletionOptions {
16089                trigger_characters: Some(vec![".".to_string()]),
16090                resolve_provider: Some(true),
16091                ..Default::default()
16092            }),
16093            ..Default::default()
16094        },
16095        cx,
16096    )
16097    .await;
16098
16099    cx.set_state("fn main() { let a = 2ˇ; }");
16100    cx.simulate_keystroke(".");
16101
16102    let item1 = lsp::CompletionItem {
16103        label: "method id()".to_string(),
16104        filter_text: Some("id".to_string()),
16105        detail: None,
16106        documentation: None,
16107        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16108            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16109            new_text: ".id".to_string(),
16110        })),
16111        ..lsp::CompletionItem::default()
16112    };
16113
16114    let item2 = lsp::CompletionItem {
16115        label: "other".to_string(),
16116        filter_text: Some("other".to_string()),
16117        detail: None,
16118        documentation: None,
16119        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16120            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16121            new_text: ".other".to_string(),
16122        })),
16123        ..lsp::CompletionItem::default()
16124    };
16125
16126    let item1 = item1.clone();
16127    cx.set_request_handler::<lsp::request::Completion, _, _>({
16128        let item1 = item1.clone();
16129        move |_, _, _| {
16130            let item1 = item1.clone();
16131            let item2 = item2.clone();
16132            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16133        }
16134    })
16135    .next()
16136    .await;
16137
16138    cx.condition(|editor, _| editor.context_menu_visible())
16139        .await;
16140    cx.update_editor(|editor, _, _| {
16141        let context_menu = editor.context_menu.borrow_mut();
16142        let context_menu = context_menu
16143            .as_ref()
16144            .expect("Should have the context menu deployed");
16145        match context_menu {
16146            CodeContextMenu::Completions(completions_menu) => {
16147                let completions = completions_menu.completions.borrow_mut();
16148                assert_eq!(
16149                    completions
16150                        .iter()
16151                        .map(|completion| &completion.label.text)
16152                        .collect::<Vec<_>>(),
16153                    vec!["method id()", "other"]
16154                )
16155            }
16156            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16157        }
16158    });
16159
16160    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16161        let item1 = item1.clone();
16162        move |_, item_to_resolve, _| {
16163            let item1 = item1.clone();
16164            async move {
16165                if item1 == item_to_resolve {
16166                    Ok(lsp::CompletionItem {
16167                        label: "method id()".to_string(),
16168                        filter_text: Some("id".to_string()),
16169                        detail: Some("Now resolved!".to_string()),
16170                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16171                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16172                            range: lsp::Range::new(
16173                                lsp::Position::new(0, 22),
16174                                lsp::Position::new(0, 22),
16175                            ),
16176                            new_text: ".id".to_string(),
16177                        })),
16178                        ..lsp::CompletionItem::default()
16179                    })
16180                } else {
16181                    Ok(item_to_resolve)
16182                }
16183            }
16184        }
16185    })
16186    .next()
16187    .await
16188    .unwrap();
16189    cx.run_until_parked();
16190
16191    cx.update_editor(|editor, window, cx| {
16192        editor.context_menu_next(&Default::default(), window, cx);
16193    });
16194
16195    cx.update_editor(|editor, _, _| {
16196        let context_menu = editor.context_menu.borrow_mut();
16197        let context_menu = context_menu
16198            .as_ref()
16199            .expect("Should have the context menu deployed");
16200        match context_menu {
16201            CodeContextMenu::Completions(completions_menu) => {
16202                let completions = completions_menu.completions.borrow_mut();
16203                assert_eq!(
16204                    completions
16205                        .iter()
16206                        .map(|completion| &completion.label.text)
16207                        .collect::<Vec<_>>(),
16208                    vec!["method id() Now resolved!", "other"],
16209                    "Should update first completion label, but not second as the filter text did not match."
16210                );
16211            }
16212            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16213        }
16214    });
16215}
16216
16217#[gpui::test]
16218async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16219    init_test(cx, |_| {});
16220    let mut cx = EditorLspTestContext::new_rust(
16221        lsp::ServerCapabilities {
16222            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16223            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16224            completion_provider: Some(lsp::CompletionOptions {
16225                resolve_provider: Some(true),
16226                ..Default::default()
16227            }),
16228            ..Default::default()
16229        },
16230        cx,
16231    )
16232    .await;
16233    cx.set_state(indoc! {"
16234        struct TestStruct {
16235            field: i32
16236        }
16237
16238        fn mainˇ() {
16239            let unused_var = 42;
16240            let test_struct = TestStruct { field: 42 };
16241        }
16242    "});
16243    let symbol_range = cx.lsp_range(indoc! {"
16244        struct TestStruct {
16245            field: i32
16246        }
16247
16248        «fn main»() {
16249            let unused_var = 42;
16250            let test_struct = TestStruct { field: 42 };
16251        }
16252    "});
16253    let mut hover_requests =
16254        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16255            Ok(Some(lsp::Hover {
16256                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16257                    kind: lsp::MarkupKind::Markdown,
16258                    value: "Function documentation".to_string(),
16259                }),
16260                range: Some(symbol_range),
16261            }))
16262        });
16263
16264    // Case 1: Test that code action menu hide hover popover
16265    cx.dispatch_action(Hover);
16266    hover_requests.next().await;
16267    cx.condition(|editor, _| editor.hover_state.visible()).await;
16268    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16269        move |_, _, _| async move {
16270            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16271                lsp::CodeAction {
16272                    title: "Remove unused variable".to_string(),
16273                    kind: Some(CodeActionKind::QUICKFIX),
16274                    edit: Some(lsp::WorkspaceEdit {
16275                        changes: Some(
16276                            [(
16277                                lsp::Url::from_file_path(path!("/file.rs")).unwrap(),
16278                                vec![lsp::TextEdit {
16279                                    range: lsp::Range::new(
16280                                        lsp::Position::new(5, 4),
16281                                        lsp::Position::new(5, 27),
16282                                    ),
16283                                    new_text: "".to_string(),
16284                                }],
16285                            )]
16286                            .into_iter()
16287                            .collect(),
16288                        ),
16289                        ..Default::default()
16290                    }),
16291                    ..Default::default()
16292                },
16293            )]))
16294        },
16295    );
16296    cx.update_editor(|editor, window, cx| {
16297        editor.toggle_code_actions(
16298            &ToggleCodeActions {
16299                deployed_from: None,
16300                quick_launch: false,
16301            },
16302            window,
16303            cx,
16304        );
16305    });
16306    code_action_requests.next().await;
16307    cx.run_until_parked();
16308    cx.condition(|editor, _| editor.context_menu_visible())
16309        .await;
16310    cx.update_editor(|editor, _, _| {
16311        assert!(
16312            !editor.hover_state.visible(),
16313            "Hover popover should be hidden when code action menu is shown"
16314        );
16315        // Hide code actions
16316        editor.context_menu.take();
16317    });
16318
16319    // Case 2: Test that code completions hide hover popover
16320    cx.dispatch_action(Hover);
16321    hover_requests.next().await;
16322    cx.condition(|editor, _| editor.hover_state.visible()).await;
16323    let counter = Arc::new(AtomicUsize::new(0));
16324    let mut completion_requests =
16325        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16326            let counter = counter.clone();
16327            async move {
16328                counter.fetch_add(1, atomic::Ordering::Release);
16329                Ok(Some(lsp::CompletionResponse::Array(vec![
16330                    lsp::CompletionItem {
16331                        label: "main".into(),
16332                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16333                        detail: Some("() -> ()".to_string()),
16334                        ..Default::default()
16335                    },
16336                    lsp::CompletionItem {
16337                        label: "TestStruct".into(),
16338                        kind: Some(lsp::CompletionItemKind::STRUCT),
16339                        detail: Some("struct TestStruct".to_string()),
16340                        ..Default::default()
16341                    },
16342                ])))
16343            }
16344        });
16345    cx.update_editor(|editor, window, cx| {
16346        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16347    });
16348    completion_requests.next().await;
16349    cx.condition(|editor, _| editor.context_menu_visible())
16350        .await;
16351    cx.update_editor(|editor, _, _| {
16352        assert!(
16353            !editor.hover_state.visible(),
16354            "Hover popover should be hidden when completion menu is shown"
16355        );
16356    });
16357}
16358
16359#[gpui::test]
16360async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16361    init_test(cx, |_| {});
16362
16363    let mut cx = EditorLspTestContext::new_rust(
16364        lsp::ServerCapabilities {
16365            completion_provider: Some(lsp::CompletionOptions {
16366                trigger_characters: Some(vec![".".to_string()]),
16367                resolve_provider: Some(true),
16368                ..Default::default()
16369            }),
16370            ..Default::default()
16371        },
16372        cx,
16373    )
16374    .await;
16375
16376    cx.set_state("fn main() { let a = 2ˇ; }");
16377    cx.simulate_keystroke(".");
16378
16379    let unresolved_item_1 = lsp::CompletionItem {
16380        label: "id".to_string(),
16381        filter_text: Some("id".to_string()),
16382        detail: None,
16383        documentation: None,
16384        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16385            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16386            new_text: ".id".to_string(),
16387        })),
16388        ..lsp::CompletionItem::default()
16389    };
16390    let resolved_item_1 = lsp::CompletionItem {
16391        additional_text_edits: Some(vec![lsp::TextEdit {
16392            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16393            new_text: "!!".to_string(),
16394        }]),
16395        ..unresolved_item_1.clone()
16396    };
16397    let unresolved_item_2 = lsp::CompletionItem {
16398        label: "other".to_string(),
16399        filter_text: Some("other".to_string()),
16400        detail: None,
16401        documentation: None,
16402        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16403            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16404            new_text: ".other".to_string(),
16405        })),
16406        ..lsp::CompletionItem::default()
16407    };
16408    let resolved_item_2 = lsp::CompletionItem {
16409        additional_text_edits: Some(vec![lsp::TextEdit {
16410            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16411            new_text: "??".to_string(),
16412        }]),
16413        ..unresolved_item_2.clone()
16414    };
16415
16416    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16417    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16418    cx.lsp
16419        .server
16420        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16421            let unresolved_item_1 = unresolved_item_1.clone();
16422            let resolved_item_1 = resolved_item_1.clone();
16423            let unresolved_item_2 = unresolved_item_2.clone();
16424            let resolved_item_2 = resolved_item_2.clone();
16425            let resolve_requests_1 = resolve_requests_1.clone();
16426            let resolve_requests_2 = resolve_requests_2.clone();
16427            move |unresolved_request, _| {
16428                let unresolved_item_1 = unresolved_item_1.clone();
16429                let resolved_item_1 = resolved_item_1.clone();
16430                let unresolved_item_2 = unresolved_item_2.clone();
16431                let resolved_item_2 = resolved_item_2.clone();
16432                let resolve_requests_1 = resolve_requests_1.clone();
16433                let resolve_requests_2 = resolve_requests_2.clone();
16434                async move {
16435                    if unresolved_request == unresolved_item_1 {
16436                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16437                        Ok(resolved_item_1.clone())
16438                    } else if unresolved_request == unresolved_item_2 {
16439                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16440                        Ok(resolved_item_2.clone())
16441                    } else {
16442                        panic!("Unexpected completion item {unresolved_request:?}")
16443                    }
16444                }
16445            }
16446        })
16447        .detach();
16448
16449    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16450        let unresolved_item_1 = unresolved_item_1.clone();
16451        let unresolved_item_2 = unresolved_item_2.clone();
16452        async move {
16453            Ok(Some(lsp::CompletionResponse::Array(vec![
16454                unresolved_item_1,
16455                unresolved_item_2,
16456            ])))
16457        }
16458    })
16459    .next()
16460    .await;
16461
16462    cx.condition(|editor, _| editor.context_menu_visible())
16463        .await;
16464    cx.update_editor(|editor, _, _| {
16465        let context_menu = editor.context_menu.borrow_mut();
16466        let context_menu = context_menu
16467            .as_ref()
16468            .expect("Should have the context menu deployed");
16469        match context_menu {
16470            CodeContextMenu::Completions(completions_menu) => {
16471                let completions = completions_menu.completions.borrow_mut();
16472                assert_eq!(
16473                    completions
16474                        .iter()
16475                        .map(|completion| &completion.label.text)
16476                        .collect::<Vec<_>>(),
16477                    vec!["id", "other"]
16478                )
16479            }
16480            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16481        }
16482    });
16483    cx.run_until_parked();
16484
16485    cx.update_editor(|editor, window, cx| {
16486        editor.context_menu_next(&ContextMenuNext, window, cx);
16487    });
16488    cx.run_until_parked();
16489    cx.update_editor(|editor, window, cx| {
16490        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16491    });
16492    cx.run_until_parked();
16493    cx.update_editor(|editor, window, cx| {
16494        editor.context_menu_next(&ContextMenuNext, window, cx);
16495    });
16496    cx.run_until_parked();
16497    cx.update_editor(|editor, window, cx| {
16498        editor
16499            .compose_completion(&ComposeCompletion::default(), window, cx)
16500            .expect("No task returned")
16501    })
16502    .await
16503    .expect("Completion failed");
16504    cx.run_until_parked();
16505
16506    cx.update_editor(|editor, _, cx| {
16507        assert_eq!(
16508            resolve_requests_1.load(atomic::Ordering::Acquire),
16509            1,
16510            "Should always resolve once despite multiple selections"
16511        );
16512        assert_eq!(
16513            resolve_requests_2.load(atomic::Ordering::Acquire),
16514            1,
16515            "Should always resolve once after multiple selections and applying the completion"
16516        );
16517        assert_eq!(
16518            editor.text(cx),
16519            "fn main() { let a = ??.other; }",
16520            "Should use resolved data when applying the completion"
16521        );
16522    });
16523}
16524
16525#[gpui::test]
16526async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16527    init_test(cx, |_| {});
16528
16529    let item_0 = lsp::CompletionItem {
16530        label: "abs".into(),
16531        insert_text: Some("abs".into()),
16532        data: Some(json!({ "very": "special"})),
16533        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16534        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16535            lsp::InsertReplaceEdit {
16536                new_text: "abs".to_string(),
16537                insert: lsp::Range::default(),
16538                replace: lsp::Range::default(),
16539            },
16540        )),
16541        ..lsp::CompletionItem::default()
16542    };
16543    let items = iter::once(item_0.clone())
16544        .chain((11..51).map(|i| lsp::CompletionItem {
16545            label: format!("item_{}", i),
16546            insert_text: Some(format!("item_{}", i)),
16547            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16548            ..lsp::CompletionItem::default()
16549        }))
16550        .collect::<Vec<_>>();
16551
16552    let default_commit_characters = vec!["?".to_string()];
16553    let default_data = json!({ "default": "data"});
16554    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16555    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16556    let default_edit_range = lsp::Range {
16557        start: lsp::Position {
16558            line: 0,
16559            character: 5,
16560        },
16561        end: lsp::Position {
16562            line: 0,
16563            character: 5,
16564        },
16565    };
16566
16567    let mut cx = EditorLspTestContext::new_rust(
16568        lsp::ServerCapabilities {
16569            completion_provider: Some(lsp::CompletionOptions {
16570                trigger_characters: Some(vec![".".to_string()]),
16571                resolve_provider: Some(true),
16572                ..Default::default()
16573            }),
16574            ..Default::default()
16575        },
16576        cx,
16577    )
16578    .await;
16579
16580    cx.set_state("fn main() { let a = 2ˇ; }");
16581    cx.simulate_keystroke(".");
16582
16583    let completion_data = default_data.clone();
16584    let completion_characters = default_commit_characters.clone();
16585    let completion_items = items.clone();
16586    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16587        let default_data = completion_data.clone();
16588        let default_commit_characters = completion_characters.clone();
16589        let items = completion_items.clone();
16590        async move {
16591            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16592                items,
16593                item_defaults: Some(lsp::CompletionListItemDefaults {
16594                    data: Some(default_data.clone()),
16595                    commit_characters: Some(default_commit_characters.clone()),
16596                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16597                        default_edit_range,
16598                    )),
16599                    insert_text_format: Some(default_insert_text_format),
16600                    insert_text_mode: Some(default_insert_text_mode),
16601                }),
16602                ..lsp::CompletionList::default()
16603            })))
16604        }
16605    })
16606    .next()
16607    .await;
16608
16609    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16610    cx.lsp
16611        .server
16612        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16613            let closure_resolved_items = resolved_items.clone();
16614            move |item_to_resolve, _| {
16615                let closure_resolved_items = closure_resolved_items.clone();
16616                async move {
16617                    closure_resolved_items.lock().push(item_to_resolve.clone());
16618                    Ok(item_to_resolve)
16619                }
16620            }
16621        })
16622        .detach();
16623
16624    cx.condition(|editor, _| editor.context_menu_visible())
16625        .await;
16626    cx.run_until_parked();
16627    cx.update_editor(|editor, _, _| {
16628        let menu = editor.context_menu.borrow_mut();
16629        match menu.as_ref().expect("should have the completions menu") {
16630            CodeContextMenu::Completions(completions_menu) => {
16631                assert_eq!(
16632                    completions_menu
16633                        .entries
16634                        .borrow()
16635                        .iter()
16636                        .map(|mat| mat.string.clone())
16637                        .collect::<Vec<String>>(),
16638                    items
16639                        .iter()
16640                        .map(|completion| completion.label.clone())
16641                        .collect::<Vec<String>>()
16642                );
16643            }
16644            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16645        }
16646    });
16647    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16648    // with 4 from the end.
16649    assert_eq!(
16650        *resolved_items.lock(),
16651        [&items[0..16], &items[items.len() - 4..items.len()]]
16652            .concat()
16653            .iter()
16654            .cloned()
16655            .map(|mut item| {
16656                if item.data.is_none() {
16657                    item.data = Some(default_data.clone());
16658                }
16659                item
16660            })
16661            .collect::<Vec<lsp::CompletionItem>>(),
16662        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16663    );
16664    resolved_items.lock().clear();
16665
16666    cx.update_editor(|editor, window, cx| {
16667        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16668    });
16669    cx.run_until_parked();
16670    // Completions that have already been resolved are skipped.
16671    assert_eq!(
16672        *resolved_items.lock(),
16673        items[items.len() - 17..items.len() - 4]
16674            .iter()
16675            .cloned()
16676            .map(|mut item| {
16677                if item.data.is_none() {
16678                    item.data = Some(default_data.clone());
16679                }
16680                item
16681            })
16682            .collect::<Vec<lsp::CompletionItem>>()
16683    );
16684    resolved_items.lock().clear();
16685}
16686
16687#[gpui::test]
16688async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16689    init_test(cx, |_| {});
16690
16691    let mut cx = EditorLspTestContext::new(
16692        Language::new(
16693            LanguageConfig {
16694                matcher: LanguageMatcher {
16695                    path_suffixes: vec!["jsx".into()],
16696                    ..Default::default()
16697                },
16698                overrides: [(
16699                    "element".into(),
16700                    LanguageConfigOverride {
16701                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16702                        ..Default::default()
16703                    },
16704                )]
16705                .into_iter()
16706                .collect(),
16707                ..Default::default()
16708            },
16709            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16710        )
16711        .with_override_query("(jsx_self_closing_element) @element")
16712        .unwrap(),
16713        lsp::ServerCapabilities {
16714            completion_provider: Some(lsp::CompletionOptions {
16715                trigger_characters: Some(vec![":".to_string()]),
16716                ..Default::default()
16717            }),
16718            ..Default::default()
16719        },
16720        cx,
16721    )
16722    .await;
16723
16724    cx.lsp
16725        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16726            Ok(Some(lsp::CompletionResponse::Array(vec![
16727                lsp::CompletionItem {
16728                    label: "bg-blue".into(),
16729                    ..Default::default()
16730                },
16731                lsp::CompletionItem {
16732                    label: "bg-red".into(),
16733                    ..Default::default()
16734                },
16735                lsp::CompletionItem {
16736                    label: "bg-yellow".into(),
16737                    ..Default::default()
16738                },
16739            ])))
16740        });
16741
16742    cx.set_state(r#"<p class="bgˇ" />"#);
16743
16744    // Trigger completion when typing a dash, because the dash is an extra
16745    // word character in the 'element' scope, which contains the cursor.
16746    cx.simulate_keystroke("-");
16747    cx.executor().run_until_parked();
16748    cx.update_editor(|editor, _, _| {
16749        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16750        {
16751            assert_eq!(
16752                completion_menu_entries(menu),
16753                &["bg-blue", "bg-red", "bg-yellow"]
16754            );
16755        } else {
16756            panic!("expected completion menu to be open");
16757        }
16758    });
16759
16760    cx.simulate_keystroke("l");
16761    cx.executor().run_until_parked();
16762    cx.update_editor(|editor, _, _| {
16763        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16764        {
16765            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
16766        } else {
16767            panic!("expected completion menu to be open");
16768        }
16769    });
16770
16771    // When filtering completions, consider the character after the '-' to
16772    // be the start of a subword.
16773    cx.set_state(r#"<p class="yelˇ" />"#);
16774    cx.simulate_keystroke("l");
16775    cx.executor().run_until_parked();
16776    cx.update_editor(|editor, _, _| {
16777        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16778        {
16779            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
16780        } else {
16781            panic!("expected completion menu to be open");
16782        }
16783    });
16784}
16785
16786fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16787    let entries = menu.entries.borrow();
16788    entries.iter().map(|mat| mat.string.clone()).collect()
16789}
16790
16791#[gpui::test]
16792async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16793    init_test(cx, |settings| {
16794        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16795            Formatter::Prettier,
16796        )))
16797    });
16798
16799    let fs = FakeFs::new(cx.executor());
16800    fs.insert_file(path!("/file.ts"), Default::default()).await;
16801
16802    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16803    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16804
16805    language_registry.add(Arc::new(Language::new(
16806        LanguageConfig {
16807            name: "TypeScript".into(),
16808            matcher: LanguageMatcher {
16809                path_suffixes: vec!["ts".to_string()],
16810                ..Default::default()
16811            },
16812            ..Default::default()
16813        },
16814        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16815    )));
16816    update_test_language_settings(cx, |settings| {
16817        settings.defaults.prettier = Some(PrettierSettings {
16818            allowed: true,
16819            ..PrettierSettings::default()
16820        });
16821    });
16822
16823    let test_plugin = "test_plugin";
16824    let _ = language_registry.register_fake_lsp(
16825        "TypeScript",
16826        FakeLspAdapter {
16827            prettier_plugins: vec![test_plugin],
16828            ..Default::default()
16829        },
16830    );
16831
16832    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16833    let buffer = project
16834        .update(cx, |project, cx| {
16835            project.open_local_buffer(path!("/file.ts"), cx)
16836        })
16837        .await
16838        .unwrap();
16839
16840    let buffer_text = "one\ntwo\nthree\n";
16841    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16842    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16843    editor.update_in(cx, |editor, window, cx| {
16844        editor.set_text(buffer_text, window, cx)
16845    });
16846
16847    editor
16848        .update_in(cx, |editor, window, cx| {
16849            editor.perform_format(
16850                project.clone(),
16851                FormatTrigger::Manual,
16852                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16853                window,
16854                cx,
16855            )
16856        })
16857        .unwrap()
16858        .await;
16859    assert_eq!(
16860        editor.update(cx, |editor, cx| editor.text(cx)),
16861        buffer_text.to_string() + prettier_format_suffix,
16862        "Test prettier formatting was not applied to the original buffer text",
16863    );
16864
16865    update_test_language_settings(cx, |settings| {
16866        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16867    });
16868    let format = editor.update_in(cx, |editor, window, cx| {
16869        editor.perform_format(
16870            project.clone(),
16871            FormatTrigger::Manual,
16872            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16873            window,
16874            cx,
16875        )
16876    });
16877    format.await.unwrap();
16878    assert_eq!(
16879        editor.update(cx, |editor, cx| editor.text(cx)),
16880        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
16881        "Autoformatting (via test prettier) was not applied to the original buffer text",
16882    );
16883}
16884
16885#[gpui::test]
16886async fn test_addition_reverts(cx: &mut TestAppContext) {
16887    init_test(cx, |_| {});
16888    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16889    let base_text = indoc! {r#"
16890        struct Row;
16891        struct Row1;
16892        struct Row2;
16893
16894        struct Row4;
16895        struct Row5;
16896        struct Row6;
16897
16898        struct Row8;
16899        struct Row9;
16900        struct Row10;"#};
16901
16902    // When addition hunks are not adjacent to carets, no hunk revert is performed
16903    assert_hunk_revert(
16904        indoc! {r#"struct Row;
16905                   struct Row1;
16906                   struct Row1.1;
16907                   struct Row1.2;
16908                   struct Row2;ˇ
16909
16910                   struct Row4;
16911                   struct Row5;
16912                   struct Row6;
16913
16914                   struct Row8;
16915                   ˇstruct Row9;
16916                   struct Row9.1;
16917                   struct Row9.2;
16918                   struct Row9.3;
16919                   struct Row10;"#},
16920        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16921        indoc! {r#"struct Row;
16922                   struct Row1;
16923                   struct Row1.1;
16924                   struct Row1.2;
16925                   struct Row2;ˇ
16926
16927                   struct Row4;
16928                   struct Row5;
16929                   struct Row6;
16930
16931                   struct Row8;
16932                   ˇstruct Row9;
16933                   struct Row9.1;
16934                   struct Row9.2;
16935                   struct Row9.3;
16936                   struct Row10;"#},
16937        base_text,
16938        &mut cx,
16939    );
16940    // Same for selections
16941    assert_hunk_revert(
16942        indoc! {r#"struct Row;
16943                   struct Row1;
16944                   struct Row2;
16945                   struct Row2.1;
16946                   struct Row2.2;
16947                   «ˇ
16948                   struct Row4;
16949                   struct» Row5;
16950                   «struct Row6;
16951                   ˇ»
16952                   struct Row9.1;
16953                   struct Row9.2;
16954                   struct Row9.3;
16955                   struct Row8;
16956                   struct Row9;
16957                   struct Row10;"#},
16958        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
16959        indoc! {r#"struct Row;
16960                   struct Row1;
16961                   struct Row2;
16962                   struct Row2.1;
16963                   struct Row2.2;
16964                   «ˇ
16965                   struct Row4;
16966                   struct» Row5;
16967                   «struct Row6;
16968                   ˇ»
16969                   struct Row9.1;
16970                   struct Row9.2;
16971                   struct Row9.3;
16972                   struct Row8;
16973                   struct Row9;
16974                   struct Row10;"#},
16975        base_text,
16976        &mut cx,
16977    );
16978
16979    // When carets and selections intersect the addition hunks, those are reverted.
16980    // Adjacent carets got merged.
16981    assert_hunk_revert(
16982        indoc! {r#"struct Row;
16983                   ˇ// something on the top
16984                   struct Row1;
16985                   struct Row2;
16986                   struct Roˇw3.1;
16987                   struct Row2.2;
16988                   struct Row2.3;ˇ
16989
16990                   struct Row4;
16991                   struct ˇRow5.1;
16992                   struct Row5.2;
16993                   struct «Rowˇ»5.3;
16994                   struct Row5;
16995                   struct Row6;
16996                   ˇ
16997                   struct Row9.1;
16998                   struct «Rowˇ»9.2;
16999                   struct «ˇRow»9.3;
17000                   struct Row8;
17001                   struct Row9;
17002                   «ˇ// something on bottom»
17003                   struct Row10;"#},
17004        vec![
17005            DiffHunkStatusKind::Added,
17006            DiffHunkStatusKind::Added,
17007            DiffHunkStatusKind::Added,
17008            DiffHunkStatusKind::Added,
17009            DiffHunkStatusKind::Added,
17010        ],
17011        indoc! {r#"struct Row;
17012                   ˇstruct Row1;
17013                   struct Row2;
17014                   ˇ
17015                   struct Row4;
17016                   ˇstruct Row5;
17017                   struct Row6;
17018                   ˇ
17019                   ˇstruct Row8;
17020                   struct Row9;
17021                   ˇstruct Row10;"#},
17022        base_text,
17023        &mut cx,
17024    );
17025}
17026
17027#[gpui::test]
17028async fn test_modification_reverts(cx: &mut TestAppContext) {
17029    init_test(cx, |_| {});
17030    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17031    let base_text = indoc! {r#"
17032        struct Row;
17033        struct Row1;
17034        struct Row2;
17035
17036        struct Row4;
17037        struct Row5;
17038        struct Row6;
17039
17040        struct Row8;
17041        struct Row9;
17042        struct Row10;"#};
17043
17044    // Modification hunks behave the same as the addition ones.
17045    assert_hunk_revert(
17046        indoc! {r#"struct Row;
17047                   struct Row1;
17048                   struct Row33;
17049                   ˇ
17050                   struct Row4;
17051                   struct Row5;
17052                   struct Row6;
17053                   ˇ
17054                   struct Row99;
17055                   struct Row9;
17056                   struct Row10;"#},
17057        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17058        indoc! {r#"struct Row;
17059                   struct Row1;
17060                   struct Row33;
17061                   ˇ
17062                   struct Row4;
17063                   struct Row5;
17064                   struct Row6;
17065                   ˇ
17066                   struct Row99;
17067                   struct Row9;
17068                   struct Row10;"#},
17069        base_text,
17070        &mut cx,
17071    );
17072    assert_hunk_revert(
17073        indoc! {r#"struct Row;
17074                   struct Row1;
17075                   struct Row33;
17076                   «ˇ
17077                   struct Row4;
17078                   struct» Row5;
17079                   «struct Row6;
17080                   ˇ»
17081                   struct Row99;
17082                   struct Row9;
17083                   struct Row10;"#},
17084        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17085        indoc! {r#"struct Row;
17086                   struct Row1;
17087                   struct Row33;
17088                   «ˇ
17089                   struct Row4;
17090                   struct» Row5;
17091                   «struct Row6;
17092                   ˇ»
17093                   struct Row99;
17094                   struct Row9;
17095                   struct Row10;"#},
17096        base_text,
17097        &mut cx,
17098    );
17099
17100    assert_hunk_revert(
17101        indoc! {r#"ˇstruct Row1.1;
17102                   struct Row1;
17103                   «ˇstr»uct Row22;
17104
17105                   struct ˇRow44;
17106                   struct Row5;
17107                   struct «Rˇ»ow66;ˇ
17108
17109                   «struˇ»ct Row88;
17110                   struct Row9;
17111                   struct Row1011;ˇ"#},
17112        vec![
17113            DiffHunkStatusKind::Modified,
17114            DiffHunkStatusKind::Modified,
17115            DiffHunkStatusKind::Modified,
17116            DiffHunkStatusKind::Modified,
17117            DiffHunkStatusKind::Modified,
17118            DiffHunkStatusKind::Modified,
17119        ],
17120        indoc! {r#"struct Row;
17121                   ˇstruct Row1;
17122                   struct Row2;
17123                   ˇ
17124                   struct Row4;
17125                   ˇstruct Row5;
17126                   struct Row6;
17127                   ˇ
17128                   struct Row8;
17129                   ˇstruct Row9;
17130                   struct Row10;ˇ"#},
17131        base_text,
17132        &mut cx,
17133    );
17134}
17135
17136#[gpui::test]
17137async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17138    init_test(cx, |_| {});
17139    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17140    let base_text = indoc! {r#"
17141        one
17142
17143        two
17144        three
17145        "#};
17146
17147    cx.set_head_text(base_text);
17148    cx.set_state("\nˇ\n");
17149    cx.executor().run_until_parked();
17150    cx.update_editor(|editor, _window, cx| {
17151        editor.expand_selected_diff_hunks(cx);
17152    });
17153    cx.executor().run_until_parked();
17154    cx.update_editor(|editor, window, cx| {
17155        editor.backspace(&Default::default(), window, cx);
17156    });
17157    cx.run_until_parked();
17158    cx.assert_state_with_diff(
17159        indoc! {r#"
17160
17161        - two
17162        - threeˇ
17163        +
17164        "#}
17165        .to_string(),
17166    );
17167}
17168
17169#[gpui::test]
17170async fn test_deletion_reverts(cx: &mut TestAppContext) {
17171    init_test(cx, |_| {});
17172    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17173    let base_text = indoc! {r#"struct Row;
17174struct Row1;
17175struct Row2;
17176
17177struct Row4;
17178struct Row5;
17179struct Row6;
17180
17181struct Row8;
17182struct Row9;
17183struct Row10;"#};
17184
17185    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17186    assert_hunk_revert(
17187        indoc! {r#"struct Row;
17188                   struct Row2;
17189
17190                   ˇstruct Row4;
17191                   struct Row5;
17192                   struct Row6;
17193                   ˇ
17194                   struct Row8;
17195                   struct Row10;"#},
17196        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17197        indoc! {r#"struct Row;
17198                   struct Row2;
17199
17200                   ˇstruct Row4;
17201                   struct Row5;
17202                   struct Row6;
17203                   ˇ
17204                   struct Row8;
17205                   struct Row10;"#},
17206        base_text,
17207        &mut cx,
17208    );
17209    assert_hunk_revert(
17210        indoc! {r#"struct Row;
17211                   struct Row2;
17212
17213                   «ˇstruct Row4;
17214                   struct» Row5;
17215                   «struct Row6;
17216                   ˇ»
17217                   struct Row8;
17218                   struct Row10;"#},
17219        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17220        indoc! {r#"struct Row;
17221                   struct Row2;
17222
17223                   «ˇstruct Row4;
17224                   struct» Row5;
17225                   «struct Row6;
17226                   ˇ»
17227                   struct Row8;
17228                   struct Row10;"#},
17229        base_text,
17230        &mut cx,
17231    );
17232
17233    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17234    assert_hunk_revert(
17235        indoc! {r#"struct Row;
17236                   ˇstruct Row2;
17237
17238                   struct Row4;
17239                   struct Row5;
17240                   struct Row6;
17241
17242                   struct Row8;ˇ
17243                   struct Row10;"#},
17244        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17245        indoc! {r#"struct Row;
17246                   struct Row1;
17247                   ˇstruct Row2;
17248
17249                   struct Row4;
17250                   struct Row5;
17251                   struct Row6;
17252
17253                   struct Row8;ˇ
17254                   struct Row9;
17255                   struct Row10;"#},
17256        base_text,
17257        &mut cx,
17258    );
17259    assert_hunk_revert(
17260        indoc! {r#"struct Row;
17261                   struct Row2«ˇ;
17262                   struct Row4;
17263                   struct» Row5;
17264                   «struct Row6;
17265
17266                   struct Row8;ˇ»
17267                   struct Row10;"#},
17268        vec![
17269            DiffHunkStatusKind::Deleted,
17270            DiffHunkStatusKind::Deleted,
17271            DiffHunkStatusKind::Deleted,
17272        ],
17273        indoc! {r#"struct Row;
17274                   struct Row1;
17275                   struct Row2«ˇ;
17276
17277                   struct Row4;
17278                   struct» Row5;
17279                   «struct Row6;
17280
17281                   struct Row8;ˇ»
17282                   struct Row9;
17283                   struct Row10;"#},
17284        base_text,
17285        &mut cx,
17286    );
17287}
17288
17289#[gpui::test]
17290async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17291    init_test(cx, |_| {});
17292
17293    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17294    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17295    let base_text_3 =
17296        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17297
17298    let text_1 = edit_first_char_of_every_line(base_text_1);
17299    let text_2 = edit_first_char_of_every_line(base_text_2);
17300    let text_3 = edit_first_char_of_every_line(base_text_3);
17301
17302    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17303    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17304    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17305
17306    let multibuffer = cx.new(|cx| {
17307        let mut multibuffer = MultiBuffer::new(ReadWrite);
17308        multibuffer.push_excerpts(
17309            buffer_1.clone(),
17310            [
17311                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17312                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17313                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17314            ],
17315            cx,
17316        );
17317        multibuffer.push_excerpts(
17318            buffer_2.clone(),
17319            [
17320                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17321                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17322                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17323            ],
17324            cx,
17325        );
17326        multibuffer.push_excerpts(
17327            buffer_3.clone(),
17328            [
17329                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17330                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17331                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17332            ],
17333            cx,
17334        );
17335        multibuffer
17336    });
17337
17338    let fs = FakeFs::new(cx.executor());
17339    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17340    let (editor, cx) = cx
17341        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17342    editor.update_in(cx, |editor, _window, cx| {
17343        for (buffer, diff_base) in [
17344            (buffer_1.clone(), base_text_1),
17345            (buffer_2.clone(), base_text_2),
17346            (buffer_3.clone(), base_text_3),
17347        ] {
17348            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17349            editor
17350                .buffer
17351                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17352        }
17353    });
17354    cx.executor().run_until_parked();
17355
17356    editor.update_in(cx, |editor, window, cx| {
17357        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}");
17358        editor.select_all(&SelectAll, window, cx);
17359        editor.git_restore(&Default::default(), window, cx);
17360    });
17361    cx.executor().run_until_parked();
17362
17363    // When all ranges are selected, all buffer hunks are reverted.
17364    editor.update(cx, |editor, cx| {
17365        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");
17366    });
17367    buffer_1.update(cx, |buffer, _| {
17368        assert_eq!(buffer.text(), base_text_1);
17369    });
17370    buffer_2.update(cx, |buffer, _| {
17371        assert_eq!(buffer.text(), base_text_2);
17372    });
17373    buffer_3.update(cx, |buffer, _| {
17374        assert_eq!(buffer.text(), base_text_3);
17375    });
17376
17377    editor.update_in(cx, |editor, window, cx| {
17378        editor.undo(&Default::default(), window, cx);
17379    });
17380
17381    editor.update_in(cx, |editor, window, cx| {
17382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17383            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17384        });
17385        editor.git_restore(&Default::default(), window, cx);
17386    });
17387
17388    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17389    // but not affect buffer_2 and its related excerpts.
17390    editor.update(cx, |editor, cx| {
17391        assert_eq!(
17392            editor.text(cx),
17393            "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}"
17394        );
17395    });
17396    buffer_1.update(cx, |buffer, _| {
17397        assert_eq!(buffer.text(), base_text_1);
17398    });
17399    buffer_2.update(cx, |buffer, _| {
17400        assert_eq!(
17401            buffer.text(),
17402            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17403        );
17404    });
17405    buffer_3.update(cx, |buffer, _| {
17406        assert_eq!(
17407            buffer.text(),
17408            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17409        );
17410    });
17411
17412    fn edit_first_char_of_every_line(text: &str) -> String {
17413        text.split('\n')
17414            .map(|line| format!("X{}", &line[1..]))
17415            .collect::<Vec<_>>()
17416            .join("\n")
17417    }
17418}
17419
17420#[gpui::test]
17421async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17422    init_test(cx, |_| {});
17423
17424    let cols = 4;
17425    let rows = 10;
17426    let sample_text_1 = sample_text(rows, cols, 'a');
17427    assert_eq!(
17428        sample_text_1,
17429        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17430    );
17431    let sample_text_2 = sample_text(rows, cols, 'l');
17432    assert_eq!(
17433        sample_text_2,
17434        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17435    );
17436    let sample_text_3 = sample_text(rows, cols, 'v');
17437    assert_eq!(
17438        sample_text_3,
17439        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17440    );
17441
17442    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17443    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17444    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17445
17446    let multi_buffer = cx.new(|cx| {
17447        let mut multibuffer = MultiBuffer::new(ReadWrite);
17448        multibuffer.push_excerpts(
17449            buffer_1.clone(),
17450            [
17451                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17452                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17453                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17454            ],
17455            cx,
17456        );
17457        multibuffer.push_excerpts(
17458            buffer_2.clone(),
17459            [
17460                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17461                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17462                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17463            ],
17464            cx,
17465        );
17466        multibuffer.push_excerpts(
17467            buffer_3.clone(),
17468            [
17469                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17470                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17471                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17472            ],
17473            cx,
17474        );
17475        multibuffer
17476    });
17477
17478    let fs = FakeFs::new(cx.executor());
17479    fs.insert_tree(
17480        "/a",
17481        json!({
17482            "main.rs": sample_text_1,
17483            "other.rs": sample_text_2,
17484            "lib.rs": sample_text_3,
17485        }),
17486    )
17487    .await;
17488    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17489    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17490    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17491    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17492        Editor::new(
17493            EditorMode::full(),
17494            multi_buffer,
17495            Some(project.clone()),
17496            window,
17497            cx,
17498        )
17499    });
17500    let multibuffer_item_id = workspace
17501        .update(cx, |workspace, window, cx| {
17502            assert!(
17503                workspace.active_item(cx).is_none(),
17504                "active item should be None before the first item is added"
17505            );
17506            workspace.add_item_to_active_pane(
17507                Box::new(multi_buffer_editor.clone()),
17508                None,
17509                true,
17510                window,
17511                cx,
17512            );
17513            let active_item = workspace
17514                .active_item(cx)
17515                .expect("should have an active item after adding the multi buffer");
17516            assert!(
17517                !active_item.is_singleton(cx),
17518                "A multi buffer was expected to active after adding"
17519            );
17520            active_item.item_id()
17521        })
17522        .unwrap();
17523    cx.executor().run_until_parked();
17524
17525    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17526        editor.change_selections(
17527            SelectionEffects::scroll(Autoscroll::Next),
17528            window,
17529            cx,
17530            |s| s.select_ranges(Some(1..2)),
17531        );
17532        editor.open_excerpts(&OpenExcerpts, window, cx);
17533    });
17534    cx.executor().run_until_parked();
17535    let first_item_id = workspace
17536        .update(cx, |workspace, window, cx| {
17537            let active_item = workspace
17538                .active_item(cx)
17539                .expect("should have an active item after navigating into the 1st buffer");
17540            let first_item_id = active_item.item_id();
17541            assert_ne!(
17542                first_item_id, multibuffer_item_id,
17543                "Should navigate into the 1st buffer and activate it"
17544            );
17545            assert!(
17546                active_item.is_singleton(cx),
17547                "New active item should be a singleton buffer"
17548            );
17549            assert_eq!(
17550                active_item
17551                    .act_as::<Editor>(cx)
17552                    .expect("should have navigated into an editor for the 1st buffer")
17553                    .read(cx)
17554                    .text(cx),
17555                sample_text_1
17556            );
17557
17558            workspace
17559                .go_back(workspace.active_pane().downgrade(), window, cx)
17560                .detach_and_log_err(cx);
17561
17562            first_item_id
17563        })
17564        .unwrap();
17565    cx.executor().run_until_parked();
17566    workspace
17567        .update(cx, |workspace, _, cx| {
17568            let active_item = workspace
17569                .active_item(cx)
17570                .expect("should have an active item after navigating back");
17571            assert_eq!(
17572                active_item.item_id(),
17573                multibuffer_item_id,
17574                "Should navigate back to the multi buffer"
17575            );
17576            assert!(!active_item.is_singleton(cx));
17577        })
17578        .unwrap();
17579
17580    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17581        editor.change_selections(
17582            SelectionEffects::scroll(Autoscroll::Next),
17583            window,
17584            cx,
17585            |s| s.select_ranges(Some(39..40)),
17586        );
17587        editor.open_excerpts(&OpenExcerpts, window, cx);
17588    });
17589    cx.executor().run_until_parked();
17590    let second_item_id = workspace
17591        .update(cx, |workspace, window, cx| {
17592            let active_item = workspace
17593                .active_item(cx)
17594                .expect("should have an active item after navigating into the 2nd buffer");
17595            let second_item_id = active_item.item_id();
17596            assert_ne!(
17597                second_item_id, multibuffer_item_id,
17598                "Should navigate away from the multibuffer"
17599            );
17600            assert_ne!(
17601                second_item_id, first_item_id,
17602                "Should navigate into the 2nd buffer and activate it"
17603            );
17604            assert!(
17605                active_item.is_singleton(cx),
17606                "New active item should be a singleton buffer"
17607            );
17608            assert_eq!(
17609                active_item
17610                    .act_as::<Editor>(cx)
17611                    .expect("should have navigated into an editor")
17612                    .read(cx)
17613                    .text(cx),
17614                sample_text_2
17615            );
17616
17617            workspace
17618                .go_back(workspace.active_pane().downgrade(), window, cx)
17619                .detach_and_log_err(cx);
17620
17621            second_item_id
17622        })
17623        .unwrap();
17624    cx.executor().run_until_parked();
17625    workspace
17626        .update(cx, |workspace, _, cx| {
17627            let active_item = workspace
17628                .active_item(cx)
17629                .expect("should have an active item after navigating back from the 2nd buffer");
17630            assert_eq!(
17631                active_item.item_id(),
17632                multibuffer_item_id,
17633                "Should navigate back from the 2nd buffer to the multi buffer"
17634            );
17635            assert!(!active_item.is_singleton(cx));
17636        })
17637        .unwrap();
17638
17639    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17640        editor.change_selections(
17641            SelectionEffects::scroll(Autoscroll::Next),
17642            window,
17643            cx,
17644            |s| s.select_ranges(Some(70..70)),
17645        );
17646        editor.open_excerpts(&OpenExcerpts, window, cx);
17647    });
17648    cx.executor().run_until_parked();
17649    workspace
17650        .update(cx, |workspace, window, cx| {
17651            let active_item = workspace
17652                .active_item(cx)
17653                .expect("should have an active item after navigating into the 3rd buffer");
17654            let third_item_id = active_item.item_id();
17655            assert_ne!(
17656                third_item_id, multibuffer_item_id,
17657                "Should navigate into the 3rd buffer and activate it"
17658            );
17659            assert_ne!(third_item_id, first_item_id);
17660            assert_ne!(third_item_id, second_item_id);
17661            assert!(
17662                active_item.is_singleton(cx),
17663                "New active item should be a singleton buffer"
17664            );
17665            assert_eq!(
17666                active_item
17667                    .act_as::<Editor>(cx)
17668                    .expect("should have navigated into an editor")
17669                    .read(cx)
17670                    .text(cx),
17671                sample_text_3
17672            );
17673
17674            workspace
17675                .go_back(workspace.active_pane().downgrade(), window, cx)
17676                .detach_and_log_err(cx);
17677        })
17678        .unwrap();
17679    cx.executor().run_until_parked();
17680    workspace
17681        .update(cx, |workspace, _, cx| {
17682            let active_item = workspace
17683                .active_item(cx)
17684                .expect("should have an active item after navigating back from the 3rd buffer");
17685            assert_eq!(
17686                active_item.item_id(),
17687                multibuffer_item_id,
17688                "Should navigate back from the 3rd buffer to the multi buffer"
17689            );
17690            assert!(!active_item.is_singleton(cx));
17691        })
17692        .unwrap();
17693}
17694
17695#[gpui::test]
17696async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17697    init_test(cx, |_| {});
17698
17699    let mut cx = EditorTestContext::new(cx).await;
17700
17701    let diff_base = r#"
17702        use some::mod;
17703
17704        const A: u32 = 42;
17705
17706        fn main() {
17707            println!("hello");
17708
17709            println!("world");
17710        }
17711        "#
17712    .unindent();
17713
17714    cx.set_state(
17715        &r#"
17716        use some::modified;
17717
17718        ˇ
17719        fn main() {
17720            println!("hello there");
17721
17722            println!("around the");
17723            println!("world");
17724        }
17725        "#
17726        .unindent(),
17727    );
17728
17729    cx.set_head_text(&diff_base);
17730    executor.run_until_parked();
17731
17732    cx.update_editor(|editor, window, cx| {
17733        editor.go_to_next_hunk(&GoToHunk, window, cx);
17734        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17735    });
17736    executor.run_until_parked();
17737    cx.assert_state_with_diff(
17738        r#"
17739          use some::modified;
17740
17741
17742          fn main() {
17743        -     println!("hello");
17744        + ˇ    println!("hello there");
17745
17746              println!("around the");
17747              println!("world");
17748          }
17749        "#
17750        .unindent(),
17751    );
17752
17753    cx.update_editor(|editor, window, cx| {
17754        for _ in 0..2 {
17755            editor.go_to_next_hunk(&GoToHunk, window, cx);
17756            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17757        }
17758    });
17759    executor.run_until_parked();
17760    cx.assert_state_with_diff(
17761        r#"
17762        - use some::mod;
17763        + ˇuse some::modified;
17764
17765
17766          fn main() {
17767        -     println!("hello");
17768        +     println!("hello there");
17769
17770        +     println!("around the");
17771              println!("world");
17772          }
17773        "#
17774        .unindent(),
17775    );
17776
17777    cx.update_editor(|editor, window, cx| {
17778        editor.go_to_next_hunk(&GoToHunk, window, cx);
17779        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17780    });
17781    executor.run_until_parked();
17782    cx.assert_state_with_diff(
17783        r#"
17784        - use some::mod;
17785        + use some::modified;
17786
17787        - const A: u32 = 42;
17788          ˇ
17789          fn main() {
17790        -     println!("hello");
17791        +     println!("hello there");
17792
17793        +     println!("around the");
17794              println!("world");
17795          }
17796        "#
17797        .unindent(),
17798    );
17799
17800    cx.update_editor(|editor, window, cx| {
17801        editor.cancel(&Cancel, window, cx);
17802    });
17803
17804    cx.assert_state_with_diff(
17805        r#"
17806          use some::modified;
17807
17808          ˇ
17809          fn main() {
17810              println!("hello there");
17811
17812              println!("around the");
17813              println!("world");
17814          }
17815        "#
17816        .unindent(),
17817    );
17818}
17819
17820#[gpui::test]
17821async fn test_diff_base_change_with_expanded_diff_hunks(
17822    executor: BackgroundExecutor,
17823    cx: &mut TestAppContext,
17824) {
17825    init_test(cx, |_| {});
17826
17827    let mut cx = EditorTestContext::new(cx).await;
17828
17829    let diff_base = r#"
17830        use some::mod1;
17831        use some::mod2;
17832
17833        const A: u32 = 42;
17834        const B: u32 = 42;
17835        const C: u32 = 42;
17836
17837        fn main() {
17838            println!("hello");
17839
17840            println!("world");
17841        }
17842        "#
17843    .unindent();
17844
17845    cx.set_state(
17846        &r#"
17847        use some::mod2;
17848
17849        const A: u32 = 42;
17850        const C: u32 = 42;
17851
17852        fn main(ˇ) {
17853            //println!("hello");
17854
17855            println!("world");
17856            //
17857            //
17858        }
17859        "#
17860        .unindent(),
17861    );
17862
17863    cx.set_head_text(&diff_base);
17864    executor.run_until_parked();
17865
17866    cx.update_editor(|editor, window, cx| {
17867        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17868    });
17869    executor.run_until_parked();
17870    cx.assert_state_with_diff(
17871        r#"
17872        - use some::mod1;
17873          use some::mod2;
17874
17875          const A: u32 = 42;
17876        - const B: u32 = 42;
17877          const C: u32 = 42;
17878
17879          fn main(ˇ) {
17880        -     println!("hello");
17881        +     //println!("hello");
17882
17883              println!("world");
17884        +     //
17885        +     //
17886          }
17887        "#
17888        .unindent(),
17889    );
17890
17891    cx.set_head_text("new diff base!");
17892    executor.run_until_parked();
17893    cx.assert_state_with_diff(
17894        r#"
17895        - new diff base!
17896        + use some::mod2;
17897        +
17898        + const A: u32 = 42;
17899        + const C: u32 = 42;
17900        +
17901        + fn main(ˇ) {
17902        +     //println!("hello");
17903        +
17904        +     println!("world");
17905        +     //
17906        +     //
17907        + }
17908        "#
17909        .unindent(),
17910    );
17911}
17912
17913#[gpui::test]
17914async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
17915    init_test(cx, |_| {});
17916
17917    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17918    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
17919    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17920    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
17921    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
17922    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
17923
17924    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
17925    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
17926    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
17927
17928    let multi_buffer = cx.new(|cx| {
17929        let mut multibuffer = MultiBuffer::new(ReadWrite);
17930        multibuffer.push_excerpts(
17931            buffer_1.clone(),
17932            [
17933                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17934                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17935                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17936            ],
17937            cx,
17938        );
17939        multibuffer.push_excerpts(
17940            buffer_2.clone(),
17941            [
17942                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17943                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17944                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17945            ],
17946            cx,
17947        );
17948        multibuffer.push_excerpts(
17949            buffer_3.clone(),
17950            [
17951                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17952                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17953                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
17954            ],
17955            cx,
17956        );
17957        multibuffer
17958    });
17959
17960    let editor =
17961        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
17962    editor
17963        .update(cx, |editor, _window, cx| {
17964            for (buffer, diff_base) in [
17965                (buffer_1.clone(), file_1_old),
17966                (buffer_2.clone(), file_2_old),
17967                (buffer_3.clone(), file_3_old),
17968            ] {
17969                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17970                editor
17971                    .buffer
17972                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17973            }
17974        })
17975        .unwrap();
17976
17977    let mut cx = EditorTestContext::for_editor(editor, cx).await;
17978    cx.run_until_parked();
17979
17980    cx.assert_editor_state(
17981        &"
17982            ˇaaa
17983            ccc
17984            ddd
17985
17986            ggg
17987            hhh
17988
17989
17990            lll
17991            mmm
17992            NNN
17993
17994            qqq
17995            rrr
17996
17997            uuu
17998            111
17999            222
18000            333
18001
18002            666
18003            777
18004
18005            000
18006            !!!"
18007        .unindent(),
18008    );
18009
18010    cx.update_editor(|editor, window, cx| {
18011        editor.select_all(&SelectAll, window, cx);
18012        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18013    });
18014    cx.executor().run_until_parked();
18015
18016    cx.assert_state_with_diff(
18017        "
18018            «aaa
18019          - bbb
18020            ccc
18021            ddd
18022
18023            ggg
18024            hhh
18025
18026
18027            lll
18028            mmm
18029          - nnn
18030          + NNN
18031
18032            qqq
18033            rrr
18034
18035            uuu
18036            111
18037            222
18038            333
18039
18040          + 666
18041            777
18042
18043            000
18044            !!!ˇ»"
18045            .unindent(),
18046    );
18047}
18048
18049#[gpui::test]
18050async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18051    init_test(cx, |_| {});
18052
18053    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18054    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18055
18056    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18057    let multi_buffer = cx.new(|cx| {
18058        let mut multibuffer = MultiBuffer::new(ReadWrite);
18059        multibuffer.push_excerpts(
18060            buffer.clone(),
18061            [
18062                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18063                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18064                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18065            ],
18066            cx,
18067        );
18068        multibuffer
18069    });
18070
18071    let editor =
18072        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18073    editor
18074        .update(cx, |editor, _window, cx| {
18075            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18076            editor
18077                .buffer
18078                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18079        })
18080        .unwrap();
18081
18082    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18083    cx.run_until_parked();
18084
18085    cx.update_editor(|editor, window, cx| {
18086        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18087    });
18088    cx.executor().run_until_parked();
18089
18090    // When the start of a hunk coincides with the start of its excerpt,
18091    // the hunk is expanded. When the start of a a hunk is earlier than
18092    // the start of its excerpt, the hunk is not expanded.
18093    cx.assert_state_with_diff(
18094        "
18095            ˇaaa
18096          - bbb
18097          + BBB
18098
18099          - ddd
18100          - eee
18101          + DDD
18102          + EEE
18103            fff
18104
18105            iii
18106        "
18107        .unindent(),
18108    );
18109}
18110
18111#[gpui::test]
18112async fn test_edits_around_expanded_insertion_hunks(
18113    executor: BackgroundExecutor,
18114    cx: &mut TestAppContext,
18115) {
18116    init_test(cx, |_| {});
18117
18118    let mut cx = EditorTestContext::new(cx).await;
18119
18120    let diff_base = r#"
18121        use some::mod1;
18122        use some::mod2;
18123
18124        const A: u32 = 42;
18125
18126        fn main() {
18127            println!("hello");
18128
18129            println!("world");
18130        }
18131        "#
18132    .unindent();
18133    executor.run_until_parked();
18134    cx.set_state(
18135        &r#"
18136        use some::mod1;
18137        use some::mod2;
18138
18139        const A: u32 = 42;
18140        const B: u32 = 42;
18141        const C: u32 = 42;
18142        ˇ
18143
18144        fn main() {
18145            println!("hello");
18146
18147            println!("world");
18148        }
18149        "#
18150        .unindent(),
18151    );
18152
18153    cx.set_head_text(&diff_base);
18154    executor.run_until_parked();
18155
18156    cx.update_editor(|editor, window, cx| {
18157        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18158    });
18159    executor.run_until_parked();
18160
18161    cx.assert_state_with_diff(
18162        r#"
18163        use some::mod1;
18164        use some::mod2;
18165
18166        const A: u32 = 42;
18167      + const B: u32 = 42;
18168      + const C: u32 = 42;
18169      + ˇ
18170
18171        fn main() {
18172            println!("hello");
18173
18174            println!("world");
18175        }
18176      "#
18177        .unindent(),
18178    );
18179
18180    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18181    executor.run_until_parked();
18182
18183    cx.assert_state_with_diff(
18184        r#"
18185        use some::mod1;
18186        use some::mod2;
18187
18188        const A: u32 = 42;
18189      + const B: u32 = 42;
18190      + const C: u32 = 42;
18191      + const D: u32 = 42;
18192      + ˇ
18193
18194        fn main() {
18195            println!("hello");
18196
18197            println!("world");
18198        }
18199      "#
18200        .unindent(),
18201    );
18202
18203    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18204    executor.run_until_parked();
18205
18206    cx.assert_state_with_diff(
18207        r#"
18208        use some::mod1;
18209        use some::mod2;
18210
18211        const A: u32 = 42;
18212      + const B: u32 = 42;
18213      + const C: u32 = 42;
18214      + const D: u32 = 42;
18215      + const E: u32 = 42;
18216      + ˇ
18217
18218        fn main() {
18219            println!("hello");
18220
18221            println!("world");
18222        }
18223      "#
18224        .unindent(),
18225    );
18226
18227    cx.update_editor(|editor, window, cx| {
18228        editor.delete_line(&DeleteLine, window, cx);
18229    });
18230    executor.run_until_parked();
18231
18232    cx.assert_state_with_diff(
18233        r#"
18234        use some::mod1;
18235        use some::mod2;
18236
18237        const A: u32 = 42;
18238      + const B: u32 = 42;
18239      + const C: u32 = 42;
18240      + const D: u32 = 42;
18241      + const E: u32 = 42;
18242        ˇ
18243        fn main() {
18244            println!("hello");
18245
18246            println!("world");
18247        }
18248      "#
18249        .unindent(),
18250    );
18251
18252    cx.update_editor(|editor, window, cx| {
18253        editor.move_up(&MoveUp, window, cx);
18254        editor.delete_line(&DeleteLine, window, cx);
18255        editor.move_up(&MoveUp, window, cx);
18256        editor.delete_line(&DeleteLine, window, cx);
18257        editor.move_up(&MoveUp, window, cx);
18258        editor.delete_line(&DeleteLine, window, cx);
18259    });
18260    executor.run_until_parked();
18261    cx.assert_state_with_diff(
18262        r#"
18263        use some::mod1;
18264        use some::mod2;
18265
18266        const A: u32 = 42;
18267      + const B: u32 = 42;
18268        ˇ
18269        fn main() {
18270            println!("hello");
18271
18272            println!("world");
18273        }
18274      "#
18275        .unindent(),
18276    );
18277
18278    cx.update_editor(|editor, window, cx| {
18279        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18280        editor.delete_line(&DeleteLine, window, cx);
18281    });
18282    executor.run_until_parked();
18283    cx.assert_state_with_diff(
18284        r#"
18285        ˇ
18286        fn main() {
18287            println!("hello");
18288
18289            println!("world");
18290        }
18291      "#
18292        .unindent(),
18293    );
18294}
18295
18296#[gpui::test]
18297async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18298    init_test(cx, |_| {});
18299
18300    let mut cx = EditorTestContext::new(cx).await;
18301    cx.set_head_text(indoc! { "
18302        one
18303        two
18304        three
18305        four
18306        five
18307        "
18308    });
18309    cx.set_state(indoc! { "
18310        one
18311        ˇthree
18312        five
18313    "});
18314    cx.run_until_parked();
18315    cx.update_editor(|editor, window, cx| {
18316        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18317    });
18318    cx.assert_state_with_diff(
18319        indoc! { "
18320        one
18321      - two
18322        ˇthree
18323      - four
18324        five
18325    "}
18326        .to_string(),
18327    );
18328    cx.update_editor(|editor, window, cx| {
18329        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18330    });
18331
18332    cx.assert_state_with_diff(
18333        indoc! { "
18334        one
18335        ˇthree
18336        five
18337    "}
18338        .to_string(),
18339    );
18340
18341    cx.set_state(indoc! { "
18342        one
18343        ˇTWO
18344        three
18345        four
18346        five
18347    "});
18348    cx.run_until_parked();
18349    cx.update_editor(|editor, window, cx| {
18350        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18351    });
18352
18353    cx.assert_state_with_diff(
18354        indoc! { "
18355            one
18356          - two
18357          + ˇTWO
18358            three
18359            four
18360            five
18361        "}
18362        .to_string(),
18363    );
18364    cx.update_editor(|editor, window, cx| {
18365        editor.move_up(&Default::default(), window, cx);
18366        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18367    });
18368    cx.assert_state_with_diff(
18369        indoc! { "
18370            one
18371            ˇTWO
18372            three
18373            four
18374            five
18375        "}
18376        .to_string(),
18377    );
18378}
18379
18380#[gpui::test]
18381async fn test_edits_around_expanded_deletion_hunks(
18382    executor: BackgroundExecutor,
18383    cx: &mut TestAppContext,
18384) {
18385    init_test(cx, |_| {});
18386
18387    let mut cx = EditorTestContext::new(cx).await;
18388
18389    let diff_base = r#"
18390        use some::mod1;
18391        use some::mod2;
18392
18393        const A: u32 = 42;
18394        const B: u32 = 42;
18395        const C: u32 = 42;
18396
18397
18398        fn main() {
18399            println!("hello");
18400
18401            println!("world");
18402        }
18403    "#
18404    .unindent();
18405    executor.run_until_parked();
18406    cx.set_state(
18407        &r#"
18408        use some::mod1;
18409        use some::mod2;
18410
18411        ˇconst B: u32 = 42;
18412        const C: u32 = 42;
18413
18414
18415        fn main() {
18416            println!("hello");
18417
18418            println!("world");
18419        }
18420        "#
18421        .unindent(),
18422    );
18423
18424    cx.set_head_text(&diff_base);
18425    executor.run_until_parked();
18426
18427    cx.update_editor(|editor, window, cx| {
18428        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18429    });
18430    executor.run_until_parked();
18431
18432    cx.assert_state_with_diff(
18433        r#"
18434        use some::mod1;
18435        use some::mod2;
18436
18437      - const A: u32 = 42;
18438        ˇconst B: u32 = 42;
18439        const C: u32 = 42;
18440
18441
18442        fn main() {
18443            println!("hello");
18444
18445            println!("world");
18446        }
18447      "#
18448        .unindent(),
18449    );
18450
18451    cx.update_editor(|editor, window, cx| {
18452        editor.delete_line(&DeleteLine, window, cx);
18453    });
18454    executor.run_until_parked();
18455    cx.assert_state_with_diff(
18456        r#"
18457        use some::mod1;
18458        use some::mod2;
18459
18460      - const A: u32 = 42;
18461      - const B: u32 = 42;
18462        ˇconst C: u32 = 42;
18463
18464
18465        fn main() {
18466            println!("hello");
18467
18468            println!("world");
18469        }
18470      "#
18471        .unindent(),
18472    );
18473
18474    cx.update_editor(|editor, window, cx| {
18475        editor.delete_line(&DeleteLine, window, cx);
18476    });
18477    executor.run_until_parked();
18478    cx.assert_state_with_diff(
18479        r#"
18480        use some::mod1;
18481        use some::mod2;
18482
18483      - const A: u32 = 42;
18484      - const B: u32 = 42;
18485      - const C: u32 = 42;
18486        ˇ
18487
18488        fn main() {
18489            println!("hello");
18490
18491            println!("world");
18492        }
18493      "#
18494        .unindent(),
18495    );
18496
18497    cx.update_editor(|editor, window, cx| {
18498        editor.handle_input("replacement", window, cx);
18499    });
18500    executor.run_until_parked();
18501    cx.assert_state_with_diff(
18502        r#"
18503        use some::mod1;
18504        use some::mod2;
18505
18506      - const A: u32 = 42;
18507      - const B: u32 = 42;
18508      - const C: u32 = 42;
18509      -
18510      + replacementˇ
18511
18512        fn main() {
18513            println!("hello");
18514
18515            println!("world");
18516        }
18517      "#
18518        .unindent(),
18519    );
18520}
18521
18522#[gpui::test]
18523async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18524    init_test(cx, |_| {});
18525
18526    let mut cx = EditorTestContext::new(cx).await;
18527
18528    let base_text = r#"
18529        one
18530        two
18531        three
18532        four
18533        five
18534    "#
18535    .unindent();
18536    executor.run_until_parked();
18537    cx.set_state(
18538        &r#"
18539        one
18540        two
18541        fˇour
18542        five
18543        "#
18544        .unindent(),
18545    );
18546
18547    cx.set_head_text(&base_text);
18548    executor.run_until_parked();
18549
18550    cx.update_editor(|editor, window, cx| {
18551        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18552    });
18553    executor.run_until_parked();
18554
18555    cx.assert_state_with_diff(
18556        r#"
18557          one
18558          two
18559        - three
18560          fˇour
18561          five
18562        "#
18563        .unindent(),
18564    );
18565
18566    cx.update_editor(|editor, window, cx| {
18567        editor.backspace(&Backspace, window, cx);
18568        editor.backspace(&Backspace, window, cx);
18569    });
18570    executor.run_until_parked();
18571    cx.assert_state_with_diff(
18572        r#"
18573          one
18574          two
18575        - threeˇ
18576        - four
18577        + our
18578          five
18579        "#
18580        .unindent(),
18581    );
18582}
18583
18584#[gpui::test]
18585async fn test_edit_after_expanded_modification_hunk(
18586    executor: BackgroundExecutor,
18587    cx: &mut TestAppContext,
18588) {
18589    init_test(cx, |_| {});
18590
18591    let mut cx = EditorTestContext::new(cx).await;
18592
18593    let diff_base = r#"
18594        use some::mod1;
18595        use some::mod2;
18596
18597        const A: u32 = 42;
18598        const B: u32 = 42;
18599        const C: u32 = 42;
18600        const D: u32 = 42;
18601
18602
18603        fn main() {
18604            println!("hello");
18605
18606            println!("world");
18607        }"#
18608    .unindent();
18609
18610    cx.set_state(
18611        &r#"
18612        use some::mod1;
18613        use some::mod2;
18614
18615        const A: u32 = 42;
18616        const B: u32 = 42;
18617        const C: u32 = 43ˇ
18618        const D: u32 = 42;
18619
18620
18621        fn main() {
18622            println!("hello");
18623
18624            println!("world");
18625        }"#
18626        .unindent(),
18627    );
18628
18629    cx.set_head_text(&diff_base);
18630    executor.run_until_parked();
18631    cx.update_editor(|editor, window, cx| {
18632        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18633    });
18634    executor.run_until_parked();
18635
18636    cx.assert_state_with_diff(
18637        r#"
18638        use some::mod1;
18639        use some::mod2;
18640
18641        const A: u32 = 42;
18642        const B: u32 = 42;
18643      - const C: u32 = 42;
18644      + const C: u32 = 43ˇ
18645        const D: u32 = 42;
18646
18647
18648        fn main() {
18649            println!("hello");
18650
18651            println!("world");
18652        }"#
18653        .unindent(),
18654    );
18655
18656    cx.update_editor(|editor, window, cx| {
18657        editor.handle_input("\nnew_line\n", window, cx);
18658    });
18659    executor.run_until_parked();
18660
18661    cx.assert_state_with_diff(
18662        r#"
18663        use some::mod1;
18664        use some::mod2;
18665
18666        const A: u32 = 42;
18667        const B: u32 = 42;
18668      - const C: u32 = 42;
18669      + const C: u32 = 43
18670      + new_line
18671      + ˇ
18672        const D: u32 = 42;
18673
18674
18675        fn main() {
18676            println!("hello");
18677
18678            println!("world");
18679        }"#
18680        .unindent(),
18681    );
18682}
18683
18684#[gpui::test]
18685async fn test_stage_and_unstage_added_file_hunk(
18686    executor: BackgroundExecutor,
18687    cx: &mut TestAppContext,
18688) {
18689    init_test(cx, |_| {});
18690
18691    let mut cx = EditorTestContext::new(cx).await;
18692    cx.update_editor(|editor, _, cx| {
18693        editor.set_expand_all_diff_hunks(cx);
18694    });
18695
18696    let working_copy = r#"
18697            ˇfn main() {
18698                println!("hello, world!");
18699            }
18700        "#
18701    .unindent();
18702
18703    cx.set_state(&working_copy);
18704    executor.run_until_parked();
18705
18706    cx.assert_state_with_diff(
18707        r#"
18708            + ˇfn main() {
18709            +     println!("hello, world!");
18710            + }
18711        "#
18712        .unindent(),
18713    );
18714    cx.assert_index_text(None);
18715
18716    cx.update_editor(|editor, window, cx| {
18717        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18718    });
18719    executor.run_until_parked();
18720    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18721    cx.assert_state_with_diff(
18722        r#"
18723            + ˇfn main() {
18724            +     println!("hello, world!");
18725            + }
18726        "#
18727        .unindent(),
18728    );
18729
18730    cx.update_editor(|editor, window, cx| {
18731        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18732    });
18733    executor.run_until_parked();
18734    cx.assert_index_text(None);
18735}
18736
18737async fn setup_indent_guides_editor(
18738    text: &str,
18739    cx: &mut TestAppContext,
18740) -> (BufferId, EditorTestContext) {
18741    init_test(cx, |_| {});
18742
18743    let mut cx = EditorTestContext::new(cx).await;
18744
18745    let buffer_id = cx.update_editor(|editor, window, cx| {
18746        editor.set_text(text, window, cx);
18747        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18748
18749        buffer_ids[0]
18750    });
18751
18752    (buffer_id, cx)
18753}
18754
18755fn assert_indent_guides(
18756    range: Range<u32>,
18757    expected: Vec<IndentGuide>,
18758    active_indices: Option<Vec<usize>>,
18759    cx: &mut EditorTestContext,
18760) {
18761    let indent_guides = cx.update_editor(|editor, window, cx| {
18762        let snapshot = editor.snapshot(window, cx).display_snapshot;
18763        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18764            editor,
18765            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18766            true,
18767            &snapshot,
18768            cx,
18769        );
18770
18771        indent_guides.sort_by(|a, b| {
18772            a.depth.cmp(&b.depth).then(
18773                a.start_row
18774                    .cmp(&b.start_row)
18775                    .then(a.end_row.cmp(&b.end_row)),
18776            )
18777        });
18778        indent_guides
18779    });
18780
18781    if let Some(expected) = active_indices {
18782        let active_indices = cx.update_editor(|editor, window, cx| {
18783            let snapshot = editor.snapshot(window, cx).display_snapshot;
18784            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18785        });
18786
18787        assert_eq!(
18788            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18789            expected,
18790            "Active indent guide indices do not match"
18791        );
18792    }
18793
18794    assert_eq!(indent_guides, expected, "Indent guides do not match");
18795}
18796
18797fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18798    IndentGuide {
18799        buffer_id,
18800        start_row: MultiBufferRow(start_row),
18801        end_row: MultiBufferRow(end_row),
18802        depth,
18803        tab_size: 4,
18804        settings: IndentGuideSettings {
18805            enabled: true,
18806            line_width: 1,
18807            active_line_width: 1,
18808            ..Default::default()
18809        },
18810    }
18811}
18812
18813#[gpui::test]
18814async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18815    let (buffer_id, mut cx) = setup_indent_guides_editor(
18816        &"
18817        fn main() {
18818            let a = 1;
18819        }"
18820        .unindent(),
18821        cx,
18822    )
18823    .await;
18824
18825    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18826}
18827
18828#[gpui::test]
18829async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18830    let (buffer_id, mut cx) = setup_indent_guides_editor(
18831        &"
18832        fn main() {
18833            let a = 1;
18834            let b = 2;
18835        }"
18836        .unindent(),
18837        cx,
18838    )
18839    .await;
18840
18841    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18842}
18843
18844#[gpui::test]
18845async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18846    let (buffer_id, mut cx) = setup_indent_guides_editor(
18847        &"
18848        fn main() {
18849            let a = 1;
18850            if a == 3 {
18851                let b = 2;
18852            } else {
18853                let c = 3;
18854            }
18855        }"
18856        .unindent(),
18857        cx,
18858    )
18859    .await;
18860
18861    assert_indent_guides(
18862        0..8,
18863        vec![
18864            indent_guide(buffer_id, 1, 6, 0),
18865            indent_guide(buffer_id, 3, 3, 1),
18866            indent_guide(buffer_id, 5, 5, 1),
18867        ],
18868        None,
18869        &mut cx,
18870    );
18871}
18872
18873#[gpui::test]
18874async fn test_indent_guide_tab(cx: &mut TestAppContext) {
18875    let (buffer_id, mut cx) = setup_indent_guides_editor(
18876        &"
18877        fn main() {
18878            let a = 1;
18879                let b = 2;
18880            let c = 3;
18881        }"
18882        .unindent(),
18883        cx,
18884    )
18885    .await;
18886
18887    assert_indent_guides(
18888        0..5,
18889        vec![
18890            indent_guide(buffer_id, 1, 3, 0),
18891            indent_guide(buffer_id, 2, 2, 1),
18892        ],
18893        None,
18894        &mut cx,
18895    );
18896}
18897
18898#[gpui::test]
18899async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
18900    let (buffer_id, mut cx) = setup_indent_guides_editor(
18901        &"
18902        fn main() {
18903            let a = 1;
18904
18905            let c = 3;
18906        }"
18907        .unindent(),
18908        cx,
18909    )
18910    .await;
18911
18912    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
18913}
18914
18915#[gpui::test]
18916async fn test_indent_guide_complex(cx: &mut TestAppContext) {
18917    let (buffer_id, mut cx) = setup_indent_guides_editor(
18918        &"
18919        fn main() {
18920            let a = 1;
18921
18922            let c = 3;
18923
18924            if a == 3 {
18925                let b = 2;
18926            } else {
18927                let c = 3;
18928            }
18929        }"
18930        .unindent(),
18931        cx,
18932    )
18933    .await;
18934
18935    assert_indent_guides(
18936        0..11,
18937        vec![
18938            indent_guide(buffer_id, 1, 9, 0),
18939            indent_guide(buffer_id, 6, 6, 1),
18940            indent_guide(buffer_id, 8, 8, 1),
18941        ],
18942        None,
18943        &mut cx,
18944    );
18945}
18946
18947#[gpui::test]
18948async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
18949    let (buffer_id, mut cx) = setup_indent_guides_editor(
18950        &"
18951        fn main() {
18952            let a = 1;
18953
18954            let c = 3;
18955
18956            if a == 3 {
18957                let b = 2;
18958            } else {
18959                let c = 3;
18960            }
18961        }"
18962        .unindent(),
18963        cx,
18964    )
18965    .await;
18966
18967    assert_indent_guides(
18968        1..11,
18969        vec![
18970            indent_guide(buffer_id, 1, 9, 0),
18971            indent_guide(buffer_id, 6, 6, 1),
18972            indent_guide(buffer_id, 8, 8, 1),
18973        ],
18974        None,
18975        &mut cx,
18976    );
18977}
18978
18979#[gpui::test]
18980async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
18981    let (buffer_id, mut cx) = setup_indent_guides_editor(
18982        &"
18983        fn main() {
18984            let a = 1;
18985
18986            let c = 3;
18987
18988            if a == 3 {
18989                let b = 2;
18990            } else {
18991                let c = 3;
18992            }
18993        }"
18994        .unindent(),
18995        cx,
18996    )
18997    .await;
18998
18999    assert_indent_guides(
19000        1..10,
19001        vec![
19002            indent_guide(buffer_id, 1, 9, 0),
19003            indent_guide(buffer_id, 6, 6, 1),
19004            indent_guide(buffer_id, 8, 8, 1),
19005        ],
19006        None,
19007        &mut cx,
19008    );
19009}
19010
19011#[gpui::test]
19012async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19013    let (buffer_id, mut cx) = setup_indent_guides_editor(
19014        &"
19015        fn main() {
19016            if a {
19017                b(
19018                    c,
19019                    d,
19020                )
19021            } else {
19022                e(
19023                    f
19024                )
19025            }
19026        }"
19027        .unindent(),
19028        cx,
19029    )
19030    .await;
19031
19032    assert_indent_guides(
19033        0..11,
19034        vec![
19035            indent_guide(buffer_id, 1, 10, 0),
19036            indent_guide(buffer_id, 2, 5, 1),
19037            indent_guide(buffer_id, 7, 9, 1),
19038            indent_guide(buffer_id, 3, 4, 2),
19039            indent_guide(buffer_id, 8, 8, 2),
19040        ],
19041        None,
19042        &mut cx,
19043    );
19044
19045    cx.update_editor(|editor, window, cx| {
19046        editor.fold_at(MultiBufferRow(2), window, cx);
19047        assert_eq!(
19048            editor.display_text(cx),
19049            "
19050            fn main() {
19051                if a {
19052                    b(⋯
19053                    )
19054                } else {
19055                    e(
19056                        f
19057                    )
19058                }
19059            }"
19060            .unindent()
19061        );
19062    });
19063
19064    assert_indent_guides(
19065        0..11,
19066        vec![
19067            indent_guide(buffer_id, 1, 10, 0),
19068            indent_guide(buffer_id, 2, 5, 1),
19069            indent_guide(buffer_id, 7, 9, 1),
19070            indent_guide(buffer_id, 8, 8, 2),
19071        ],
19072        None,
19073        &mut cx,
19074    );
19075}
19076
19077#[gpui::test]
19078async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19079    let (buffer_id, mut cx) = setup_indent_guides_editor(
19080        &"
19081        block1
19082            block2
19083                block3
19084                    block4
19085            block2
19086        block1
19087        block1"
19088            .unindent(),
19089        cx,
19090    )
19091    .await;
19092
19093    assert_indent_guides(
19094        1..10,
19095        vec![
19096            indent_guide(buffer_id, 1, 4, 0),
19097            indent_guide(buffer_id, 2, 3, 1),
19098            indent_guide(buffer_id, 3, 3, 2),
19099        ],
19100        None,
19101        &mut cx,
19102    );
19103}
19104
19105#[gpui::test]
19106async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19107    let (buffer_id, mut cx) = setup_indent_guides_editor(
19108        &"
19109        block1
19110            block2
19111                block3
19112
19113        block1
19114        block1"
19115            .unindent(),
19116        cx,
19117    )
19118    .await;
19119
19120    assert_indent_guides(
19121        0..6,
19122        vec![
19123            indent_guide(buffer_id, 1, 2, 0),
19124            indent_guide(buffer_id, 2, 2, 1),
19125        ],
19126        None,
19127        &mut cx,
19128    );
19129}
19130
19131#[gpui::test]
19132async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19133    let (buffer_id, mut cx) = setup_indent_guides_editor(
19134        &"
19135        function component() {
19136        \treturn (
19137        \t\t\t
19138        \t\t<div>
19139        \t\t\t<abc></abc>
19140        \t\t</div>
19141        \t)
19142        }"
19143        .unindent(),
19144        cx,
19145    )
19146    .await;
19147
19148    assert_indent_guides(
19149        0..8,
19150        vec![
19151            indent_guide(buffer_id, 1, 6, 0),
19152            indent_guide(buffer_id, 2, 5, 1),
19153            indent_guide(buffer_id, 4, 4, 2),
19154        ],
19155        None,
19156        &mut cx,
19157    );
19158}
19159
19160#[gpui::test]
19161async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19162    let (buffer_id, mut cx) = setup_indent_guides_editor(
19163        &"
19164        function component() {
19165        \treturn (
19166        \t
19167        \t\t<div>
19168        \t\t\t<abc></abc>
19169        \t\t</div>
19170        \t)
19171        }"
19172        .unindent(),
19173        cx,
19174    )
19175    .await;
19176
19177    assert_indent_guides(
19178        0..8,
19179        vec![
19180            indent_guide(buffer_id, 1, 6, 0),
19181            indent_guide(buffer_id, 2, 5, 1),
19182            indent_guide(buffer_id, 4, 4, 2),
19183        ],
19184        None,
19185        &mut cx,
19186    );
19187}
19188
19189#[gpui::test]
19190async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19191    let (buffer_id, mut cx) = setup_indent_guides_editor(
19192        &"
19193        block1
19194
19195
19196
19197            block2
19198        "
19199        .unindent(),
19200        cx,
19201    )
19202    .await;
19203
19204    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19205}
19206
19207#[gpui::test]
19208async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19209    let (buffer_id, mut cx) = setup_indent_guides_editor(
19210        &"
19211        def a:
19212        \tb = 3
19213        \tif True:
19214        \t\tc = 4
19215        \t\td = 5
19216        \tprint(b)
19217        "
19218        .unindent(),
19219        cx,
19220    )
19221    .await;
19222
19223    assert_indent_guides(
19224        0..6,
19225        vec![
19226            indent_guide(buffer_id, 1, 5, 0),
19227            indent_guide(buffer_id, 3, 4, 1),
19228        ],
19229        None,
19230        &mut cx,
19231    );
19232}
19233
19234#[gpui::test]
19235async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19236    let (buffer_id, mut cx) = setup_indent_guides_editor(
19237        &"
19238    fn main() {
19239        let a = 1;
19240    }"
19241        .unindent(),
19242        cx,
19243    )
19244    .await;
19245
19246    cx.update_editor(|editor, window, cx| {
19247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19248            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19249        });
19250    });
19251
19252    assert_indent_guides(
19253        0..3,
19254        vec![indent_guide(buffer_id, 1, 1, 0)],
19255        Some(vec![0]),
19256        &mut cx,
19257    );
19258}
19259
19260#[gpui::test]
19261async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19262    let (buffer_id, mut cx) = setup_indent_guides_editor(
19263        &"
19264    fn main() {
19265        if 1 == 2 {
19266            let a = 1;
19267        }
19268    }"
19269        .unindent(),
19270        cx,
19271    )
19272    .await;
19273
19274    cx.update_editor(|editor, window, cx| {
19275        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19276            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19277        });
19278    });
19279
19280    assert_indent_guides(
19281        0..4,
19282        vec![
19283            indent_guide(buffer_id, 1, 3, 0),
19284            indent_guide(buffer_id, 2, 2, 1),
19285        ],
19286        Some(vec![1]),
19287        &mut cx,
19288    );
19289
19290    cx.update_editor(|editor, window, cx| {
19291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19292            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19293        });
19294    });
19295
19296    assert_indent_guides(
19297        0..4,
19298        vec![
19299            indent_guide(buffer_id, 1, 3, 0),
19300            indent_guide(buffer_id, 2, 2, 1),
19301        ],
19302        Some(vec![1]),
19303        &mut cx,
19304    );
19305
19306    cx.update_editor(|editor, window, cx| {
19307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19308            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19309        });
19310    });
19311
19312    assert_indent_guides(
19313        0..4,
19314        vec![
19315            indent_guide(buffer_id, 1, 3, 0),
19316            indent_guide(buffer_id, 2, 2, 1),
19317        ],
19318        Some(vec![0]),
19319        &mut cx,
19320    );
19321}
19322
19323#[gpui::test]
19324async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19325    let (buffer_id, mut cx) = setup_indent_guides_editor(
19326        &"
19327    fn main() {
19328        let a = 1;
19329
19330        let b = 2;
19331    }"
19332        .unindent(),
19333        cx,
19334    )
19335    .await;
19336
19337    cx.update_editor(|editor, window, cx| {
19338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19339            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19340        });
19341    });
19342
19343    assert_indent_guides(
19344        0..5,
19345        vec![indent_guide(buffer_id, 1, 3, 0)],
19346        Some(vec![0]),
19347        &mut cx,
19348    );
19349}
19350
19351#[gpui::test]
19352async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19353    let (buffer_id, mut cx) = setup_indent_guides_editor(
19354        &"
19355    def m:
19356        a = 1
19357        pass"
19358            .unindent(),
19359        cx,
19360    )
19361    .await;
19362
19363    cx.update_editor(|editor, window, cx| {
19364        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19365            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19366        });
19367    });
19368
19369    assert_indent_guides(
19370        0..3,
19371        vec![indent_guide(buffer_id, 1, 2, 0)],
19372        Some(vec![0]),
19373        &mut cx,
19374    );
19375}
19376
19377#[gpui::test]
19378async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19379    init_test(cx, |_| {});
19380    let mut cx = EditorTestContext::new(cx).await;
19381    let text = indoc! {
19382        "
19383        impl A {
19384            fn b() {
19385                0;
19386                3;
19387                5;
19388                6;
19389                7;
19390            }
19391        }
19392        "
19393    };
19394    let base_text = indoc! {
19395        "
19396        impl A {
19397            fn b() {
19398                0;
19399                1;
19400                2;
19401                3;
19402                4;
19403            }
19404            fn c() {
19405                5;
19406                6;
19407                7;
19408            }
19409        }
19410        "
19411    };
19412
19413    cx.update_editor(|editor, window, cx| {
19414        editor.set_text(text, window, cx);
19415
19416        editor.buffer().update(cx, |multibuffer, cx| {
19417            let buffer = multibuffer.as_singleton().unwrap();
19418            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19419
19420            multibuffer.set_all_diff_hunks_expanded(cx);
19421            multibuffer.add_diff(diff, cx);
19422
19423            buffer.read(cx).remote_id()
19424        })
19425    });
19426    cx.run_until_parked();
19427
19428    cx.assert_state_with_diff(
19429        indoc! { "
19430          impl A {
19431              fn b() {
19432                  0;
19433        -         1;
19434        -         2;
19435                  3;
19436        -         4;
19437        -     }
19438        -     fn c() {
19439                  5;
19440                  6;
19441                  7;
19442              }
19443          }
19444          ˇ"
19445        }
19446        .to_string(),
19447    );
19448
19449    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19450        editor
19451            .snapshot(window, cx)
19452            .buffer_snapshot
19453            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19454            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19455            .collect::<Vec<_>>()
19456    });
19457    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19458    assert_eq!(
19459        actual_guides,
19460        vec![
19461            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19462            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19463            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19464        ]
19465    );
19466}
19467
19468#[gpui::test]
19469async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19470    init_test(cx, |_| {});
19471    let mut cx = EditorTestContext::new(cx).await;
19472
19473    let diff_base = r#"
19474        a
19475        b
19476        c
19477        "#
19478    .unindent();
19479
19480    cx.set_state(
19481        &r#"
19482        ˇA
19483        b
19484        C
19485        "#
19486        .unindent(),
19487    );
19488    cx.set_head_text(&diff_base);
19489    cx.update_editor(|editor, window, cx| {
19490        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19491    });
19492    executor.run_until_parked();
19493
19494    let both_hunks_expanded = r#"
19495        - a
19496        + ˇA
19497          b
19498        - c
19499        + C
19500        "#
19501    .unindent();
19502
19503    cx.assert_state_with_diff(both_hunks_expanded.clone());
19504
19505    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19506        let snapshot = editor.snapshot(window, cx);
19507        let hunks = editor
19508            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19509            .collect::<Vec<_>>();
19510        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19511        let buffer_id = hunks[0].buffer_id;
19512        hunks
19513            .into_iter()
19514            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19515            .collect::<Vec<_>>()
19516    });
19517    assert_eq!(hunk_ranges.len(), 2);
19518
19519    cx.update_editor(|editor, _, cx| {
19520        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19521    });
19522    executor.run_until_parked();
19523
19524    let second_hunk_expanded = r#"
19525          ˇA
19526          b
19527        - c
19528        + C
19529        "#
19530    .unindent();
19531
19532    cx.assert_state_with_diff(second_hunk_expanded);
19533
19534    cx.update_editor(|editor, _, cx| {
19535        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19536    });
19537    executor.run_until_parked();
19538
19539    cx.assert_state_with_diff(both_hunks_expanded.clone());
19540
19541    cx.update_editor(|editor, _, cx| {
19542        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19543    });
19544    executor.run_until_parked();
19545
19546    let first_hunk_expanded = r#"
19547        - a
19548        + ˇA
19549          b
19550          C
19551        "#
19552    .unindent();
19553
19554    cx.assert_state_with_diff(first_hunk_expanded);
19555
19556    cx.update_editor(|editor, _, cx| {
19557        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19558    });
19559    executor.run_until_parked();
19560
19561    cx.assert_state_with_diff(both_hunks_expanded);
19562
19563    cx.set_state(
19564        &r#"
19565        ˇA
19566        b
19567        "#
19568        .unindent(),
19569    );
19570    cx.run_until_parked();
19571
19572    // TODO this cursor position seems bad
19573    cx.assert_state_with_diff(
19574        r#"
19575        - ˇa
19576        + A
19577          b
19578        "#
19579        .unindent(),
19580    );
19581
19582    cx.update_editor(|editor, window, cx| {
19583        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19584    });
19585
19586    cx.assert_state_with_diff(
19587        r#"
19588            - ˇa
19589            + A
19590              b
19591            - c
19592            "#
19593        .unindent(),
19594    );
19595
19596    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19597        let snapshot = editor.snapshot(window, cx);
19598        let hunks = editor
19599            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19600            .collect::<Vec<_>>();
19601        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19602        let buffer_id = hunks[0].buffer_id;
19603        hunks
19604            .into_iter()
19605            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19606            .collect::<Vec<_>>()
19607    });
19608    assert_eq!(hunk_ranges.len(), 2);
19609
19610    cx.update_editor(|editor, _, cx| {
19611        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19612    });
19613    executor.run_until_parked();
19614
19615    cx.assert_state_with_diff(
19616        r#"
19617        - ˇa
19618        + A
19619          b
19620        "#
19621        .unindent(),
19622    );
19623}
19624
19625#[gpui::test]
19626async fn test_toggle_deletion_hunk_at_start_of_file(
19627    executor: BackgroundExecutor,
19628    cx: &mut TestAppContext,
19629) {
19630    init_test(cx, |_| {});
19631    let mut cx = EditorTestContext::new(cx).await;
19632
19633    let diff_base = r#"
19634        a
19635        b
19636        c
19637        "#
19638    .unindent();
19639
19640    cx.set_state(
19641        &r#"
19642        ˇb
19643        c
19644        "#
19645        .unindent(),
19646    );
19647    cx.set_head_text(&diff_base);
19648    cx.update_editor(|editor, window, cx| {
19649        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19650    });
19651    executor.run_until_parked();
19652
19653    let hunk_expanded = r#"
19654        - a
19655          ˇb
19656          c
19657        "#
19658    .unindent();
19659
19660    cx.assert_state_with_diff(hunk_expanded.clone());
19661
19662    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19663        let snapshot = editor.snapshot(window, cx);
19664        let hunks = editor
19665            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19666            .collect::<Vec<_>>();
19667        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19668        let buffer_id = hunks[0].buffer_id;
19669        hunks
19670            .into_iter()
19671            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19672            .collect::<Vec<_>>()
19673    });
19674    assert_eq!(hunk_ranges.len(), 1);
19675
19676    cx.update_editor(|editor, _, cx| {
19677        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19678    });
19679    executor.run_until_parked();
19680
19681    let hunk_collapsed = r#"
19682          ˇb
19683          c
19684        "#
19685    .unindent();
19686
19687    cx.assert_state_with_diff(hunk_collapsed);
19688
19689    cx.update_editor(|editor, _, cx| {
19690        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19691    });
19692    executor.run_until_parked();
19693
19694    cx.assert_state_with_diff(hunk_expanded);
19695}
19696
19697#[gpui::test]
19698async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19699    init_test(cx, |_| {});
19700
19701    let fs = FakeFs::new(cx.executor());
19702    fs.insert_tree(
19703        path!("/test"),
19704        json!({
19705            ".git": {},
19706            "file-1": "ONE\n",
19707            "file-2": "TWO\n",
19708            "file-3": "THREE\n",
19709        }),
19710    )
19711    .await;
19712
19713    fs.set_head_for_repo(
19714        path!("/test/.git").as_ref(),
19715        &[
19716            ("file-1".into(), "one\n".into()),
19717            ("file-2".into(), "two\n".into()),
19718            ("file-3".into(), "three\n".into()),
19719        ],
19720        "deadbeef",
19721    );
19722
19723    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19724    let mut buffers = vec![];
19725    for i in 1..=3 {
19726        let buffer = project
19727            .update(cx, |project, cx| {
19728                let path = format!(path!("/test/file-{}"), i);
19729                project.open_local_buffer(path, cx)
19730            })
19731            .await
19732            .unwrap();
19733        buffers.push(buffer);
19734    }
19735
19736    let multibuffer = cx.new(|cx| {
19737        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19738        multibuffer.set_all_diff_hunks_expanded(cx);
19739        for buffer in &buffers {
19740            let snapshot = buffer.read(cx).snapshot();
19741            multibuffer.set_excerpts_for_path(
19742                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19743                buffer.clone(),
19744                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19745                DEFAULT_MULTIBUFFER_CONTEXT,
19746                cx,
19747            );
19748        }
19749        multibuffer
19750    });
19751
19752    let editor = cx.add_window(|window, cx| {
19753        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19754    });
19755    cx.run_until_parked();
19756
19757    let snapshot = editor
19758        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19759        .unwrap();
19760    let hunks = snapshot
19761        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19762        .map(|hunk| match hunk {
19763            DisplayDiffHunk::Unfolded {
19764                display_row_range, ..
19765            } => display_row_range,
19766            DisplayDiffHunk::Folded { .. } => unreachable!(),
19767        })
19768        .collect::<Vec<_>>();
19769    assert_eq!(
19770        hunks,
19771        [
19772            DisplayRow(2)..DisplayRow(4),
19773            DisplayRow(7)..DisplayRow(9),
19774            DisplayRow(12)..DisplayRow(14),
19775        ]
19776    );
19777}
19778
19779#[gpui::test]
19780async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19781    init_test(cx, |_| {});
19782
19783    let mut cx = EditorTestContext::new(cx).await;
19784    cx.set_head_text(indoc! { "
19785        one
19786        two
19787        three
19788        four
19789        five
19790        "
19791    });
19792    cx.set_index_text(indoc! { "
19793        one
19794        two
19795        three
19796        four
19797        five
19798        "
19799    });
19800    cx.set_state(indoc! {"
19801        one
19802        TWO
19803        ˇTHREE
19804        FOUR
19805        five
19806    "});
19807    cx.run_until_parked();
19808    cx.update_editor(|editor, window, cx| {
19809        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19810    });
19811    cx.run_until_parked();
19812    cx.assert_index_text(Some(indoc! {"
19813        one
19814        TWO
19815        THREE
19816        FOUR
19817        five
19818    "}));
19819    cx.set_state(indoc! { "
19820        one
19821        TWO
19822        ˇTHREE-HUNDRED
19823        FOUR
19824        five
19825    "});
19826    cx.run_until_parked();
19827    cx.update_editor(|editor, window, cx| {
19828        let snapshot = editor.snapshot(window, cx);
19829        let hunks = editor
19830            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19831            .collect::<Vec<_>>();
19832        assert_eq!(hunks.len(), 1);
19833        assert_eq!(
19834            hunks[0].status(),
19835            DiffHunkStatus {
19836                kind: DiffHunkStatusKind::Modified,
19837                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19838            }
19839        );
19840
19841        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19842    });
19843    cx.run_until_parked();
19844    cx.assert_index_text(Some(indoc! {"
19845        one
19846        TWO
19847        THREE-HUNDRED
19848        FOUR
19849        five
19850    "}));
19851}
19852
19853#[gpui::test]
19854fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19855    init_test(cx, |_| {});
19856
19857    let editor = cx.add_window(|window, cx| {
19858        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19859        build_editor(buffer, window, cx)
19860    });
19861
19862    let render_args = Arc::new(Mutex::new(None));
19863    let snapshot = editor
19864        .update(cx, |editor, window, cx| {
19865            let snapshot = editor.buffer().read(cx).snapshot(cx);
19866            let range =
19867                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19868
19869            struct RenderArgs {
19870                row: MultiBufferRow,
19871                folded: bool,
19872                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19873            }
19874
19875            let crease = Crease::inline(
19876                range,
19877                FoldPlaceholder::test(),
19878                {
19879                    let toggle_callback = render_args.clone();
19880                    move |row, folded, callback, _window, _cx| {
19881                        *toggle_callback.lock() = Some(RenderArgs {
19882                            row,
19883                            folded,
19884                            callback,
19885                        });
19886                        div()
19887                    }
19888                },
19889                |_row, _folded, _window, _cx| div(),
19890            );
19891
19892            editor.insert_creases(Some(crease), cx);
19893            let snapshot = editor.snapshot(window, cx);
19894            let _div =
19895                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
19896            snapshot
19897        })
19898        .unwrap();
19899
19900    let render_args = render_args.lock().take().unwrap();
19901    assert_eq!(render_args.row, MultiBufferRow(1));
19902    assert!(!render_args.folded);
19903    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19904
19905    cx.update_window(*editor, |_, window, cx| {
19906        (render_args.callback)(true, window, cx)
19907    })
19908    .unwrap();
19909    let snapshot = editor
19910        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19911        .unwrap();
19912    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
19913
19914    cx.update_window(*editor, |_, window, cx| {
19915        (render_args.callback)(false, window, cx)
19916    })
19917    .unwrap();
19918    let snapshot = editor
19919        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19920        .unwrap();
19921    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
19922}
19923
19924#[gpui::test]
19925async fn test_input_text(cx: &mut TestAppContext) {
19926    init_test(cx, |_| {});
19927    let mut cx = EditorTestContext::new(cx).await;
19928
19929    cx.set_state(
19930        &r#"ˇone
19931        two
19932
19933        three
19934        fourˇ
19935        five
19936
19937        siˇx"#
19938            .unindent(),
19939    );
19940
19941    cx.dispatch_action(HandleInput(String::new()));
19942    cx.assert_editor_state(
19943        &r#"ˇone
19944        two
19945
19946        three
19947        fourˇ
19948        five
19949
19950        siˇx"#
19951            .unindent(),
19952    );
19953
19954    cx.dispatch_action(HandleInput("AAAA".to_string()));
19955    cx.assert_editor_state(
19956        &r#"AAAAˇone
19957        two
19958
19959        three
19960        fourAAAAˇ
19961        five
19962
19963        siAAAAˇx"#
19964            .unindent(),
19965    );
19966}
19967
19968#[gpui::test]
19969async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
19970    init_test(cx, |_| {});
19971
19972    let mut cx = EditorTestContext::new(cx).await;
19973    cx.set_state(
19974        r#"let foo = 1;
19975let foo = 2;
19976let foo = 3;
19977let fooˇ = 4;
19978let foo = 5;
19979let foo = 6;
19980let foo = 7;
19981let foo = 8;
19982let foo = 9;
19983let foo = 10;
19984let foo = 11;
19985let foo = 12;
19986let foo = 13;
19987let foo = 14;
19988let foo = 15;"#,
19989    );
19990
19991    cx.update_editor(|e, window, cx| {
19992        assert_eq!(
19993            e.next_scroll_position,
19994            NextScrollCursorCenterTopBottom::Center,
19995            "Default next scroll direction is center",
19996        );
19997
19998        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
19999        assert_eq!(
20000            e.next_scroll_position,
20001            NextScrollCursorCenterTopBottom::Top,
20002            "After center, next scroll direction should be top",
20003        );
20004
20005        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20006        assert_eq!(
20007            e.next_scroll_position,
20008            NextScrollCursorCenterTopBottom::Bottom,
20009            "After top, next scroll direction should be bottom",
20010        );
20011
20012        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20013        assert_eq!(
20014            e.next_scroll_position,
20015            NextScrollCursorCenterTopBottom::Center,
20016            "After bottom, scrolling should start over",
20017        );
20018
20019        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20020        assert_eq!(
20021            e.next_scroll_position,
20022            NextScrollCursorCenterTopBottom::Top,
20023            "Scrolling continues if retriggered fast enough"
20024        );
20025    });
20026
20027    cx.executor()
20028        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20029    cx.executor().run_until_parked();
20030    cx.update_editor(|e, _, _| {
20031        assert_eq!(
20032            e.next_scroll_position,
20033            NextScrollCursorCenterTopBottom::Center,
20034            "If scrolling is not triggered fast enough, it should reset"
20035        );
20036    });
20037}
20038
20039#[gpui::test]
20040async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20041    init_test(cx, |_| {});
20042    let mut cx = EditorLspTestContext::new_rust(
20043        lsp::ServerCapabilities {
20044            definition_provider: Some(lsp::OneOf::Left(true)),
20045            references_provider: Some(lsp::OneOf::Left(true)),
20046            ..lsp::ServerCapabilities::default()
20047        },
20048        cx,
20049    )
20050    .await;
20051
20052    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20053        let go_to_definition = cx
20054            .lsp
20055            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20056                move |params, _| async move {
20057                    if empty_go_to_definition {
20058                        Ok(None)
20059                    } else {
20060                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20061                            uri: params.text_document_position_params.text_document.uri,
20062                            range: lsp::Range::new(
20063                                lsp::Position::new(4, 3),
20064                                lsp::Position::new(4, 6),
20065                            ),
20066                        })))
20067                    }
20068                },
20069            );
20070        let references = cx
20071            .lsp
20072            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20073                Ok(Some(vec![lsp::Location {
20074                    uri: params.text_document_position.text_document.uri,
20075                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20076                }]))
20077            });
20078        (go_to_definition, references)
20079    };
20080
20081    cx.set_state(
20082        &r#"fn one() {
20083            let mut a = ˇtwo();
20084        }
20085
20086        fn two() {}"#
20087            .unindent(),
20088    );
20089    set_up_lsp_handlers(false, &mut cx);
20090    let navigated = cx
20091        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20092        .await
20093        .expect("Failed to navigate to definition");
20094    assert_eq!(
20095        navigated,
20096        Navigated::Yes,
20097        "Should have navigated to definition from the GetDefinition response"
20098    );
20099    cx.assert_editor_state(
20100        &r#"fn one() {
20101            let mut a = two();
20102        }
20103
20104        fn «twoˇ»() {}"#
20105            .unindent(),
20106    );
20107
20108    let editors = cx.update_workspace(|workspace, _, cx| {
20109        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20110    });
20111    cx.update_editor(|_, _, test_editor_cx| {
20112        assert_eq!(
20113            editors.len(),
20114            1,
20115            "Initially, only one, test, editor should be open in the workspace"
20116        );
20117        assert_eq!(
20118            test_editor_cx.entity(),
20119            editors.last().expect("Asserted len is 1").clone()
20120        );
20121    });
20122
20123    set_up_lsp_handlers(true, &mut cx);
20124    let navigated = cx
20125        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20126        .await
20127        .expect("Failed to navigate to lookup references");
20128    assert_eq!(
20129        navigated,
20130        Navigated::Yes,
20131        "Should have navigated to references as a fallback after empty GoToDefinition response"
20132    );
20133    // We should not change the selections in the existing file,
20134    // if opening another milti buffer with the references
20135    cx.assert_editor_state(
20136        &r#"fn one() {
20137            let mut a = two();
20138        }
20139
20140        fn «twoˇ»() {}"#
20141            .unindent(),
20142    );
20143    let editors = cx.update_workspace(|workspace, _, cx| {
20144        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20145    });
20146    cx.update_editor(|_, _, test_editor_cx| {
20147        assert_eq!(
20148            editors.len(),
20149            2,
20150            "After falling back to references search, we open a new editor with the results"
20151        );
20152        let references_fallback_text = editors
20153            .into_iter()
20154            .find(|new_editor| *new_editor != test_editor_cx.entity())
20155            .expect("Should have one non-test editor now")
20156            .read(test_editor_cx)
20157            .text(test_editor_cx);
20158        assert_eq!(
20159            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20160            "Should use the range from the references response and not the GoToDefinition one"
20161        );
20162    });
20163}
20164
20165#[gpui::test]
20166async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20167    init_test(cx, |_| {});
20168    cx.update(|cx| {
20169        let mut editor_settings = EditorSettings::get_global(cx).clone();
20170        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20171        EditorSettings::override_global(editor_settings, cx);
20172    });
20173    let mut cx = EditorLspTestContext::new_rust(
20174        lsp::ServerCapabilities {
20175            definition_provider: Some(lsp::OneOf::Left(true)),
20176            references_provider: Some(lsp::OneOf::Left(true)),
20177            ..lsp::ServerCapabilities::default()
20178        },
20179        cx,
20180    )
20181    .await;
20182    let original_state = r#"fn one() {
20183        let mut a = ˇtwo();
20184    }
20185
20186    fn two() {}"#
20187        .unindent();
20188    cx.set_state(&original_state);
20189
20190    let mut go_to_definition = cx
20191        .lsp
20192        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20193            move |_, _| async move { Ok(None) },
20194        );
20195    let _references = cx
20196        .lsp
20197        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20198            panic!("Should not call for references with no go to definition fallback")
20199        });
20200
20201    let navigated = cx
20202        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20203        .await
20204        .expect("Failed to navigate to lookup references");
20205    go_to_definition
20206        .next()
20207        .await
20208        .expect("Should have called the go_to_definition handler");
20209
20210    assert_eq!(
20211        navigated,
20212        Navigated::No,
20213        "Should have navigated to references as a fallback after empty GoToDefinition response"
20214    );
20215    cx.assert_editor_state(&original_state);
20216    let editors = cx.update_workspace(|workspace, _, cx| {
20217        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20218    });
20219    cx.update_editor(|_, _, _| {
20220        assert_eq!(
20221            editors.len(),
20222            1,
20223            "After unsuccessful fallback, no other editor should have been opened"
20224        );
20225    });
20226}
20227
20228#[gpui::test]
20229async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20230    init_test(cx, |_| {});
20231
20232    let language = Arc::new(Language::new(
20233        LanguageConfig::default(),
20234        Some(tree_sitter_rust::LANGUAGE.into()),
20235    ));
20236
20237    let text = r#"
20238        #[cfg(test)]
20239        mod tests() {
20240            #[test]
20241            fn runnable_1() {
20242                let a = 1;
20243            }
20244
20245            #[test]
20246            fn runnable_2() {
20247                let a = 1;
20248                let b = 2;
20249            }
20250        }
20251    "#
20252    .unindent();
20253
20254    let fs = FakeFs::new(cx.executor());
20255    fs.insert_file("/file.rs", Default::default()).await;
20256
20257    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20258    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20259    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20260    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20261    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20262
20263    let editor = cx.new_window_entity(|window, cx| {
20264        Editor::new(
20265            EditorMode::full(),
20266            multi_buffer,
20267            Some(project.clone()),
20268            window,
20269            cx,
20270        )
20271    });
20272
20273    editor.update_in(cx, |editor, window, cx| {
20274        let snapshot = editor.buffer().read(cx).snapshot(cx);
20275        editor.tasks.insert(
20276            (buffer.read(cx).remote_id(), 3),
20277            RunnableTasks {
20278                templates: vec![],
20279                offset: snapshot.anchor_before(43),
20280                column: 0,
20281                extra_variables: HashMap::default(),
20282                context_range: BufferOffset(43)..BufferOffset(85),
20283            },
20284        );
20285        editor.tasks.insert(
20286            (buffer.read(cx).remote_id(), 8),
20287            RunnableTasks {
20288                templates: vec![],
20289                offset: snapshot.anchor_before(86),
20290                column: 0,
20291                extra_variables: HashMap::default(),
20292                context_range: BufferOffset(86)..BufferOffset(191),
20293            },
20294        );
20295
20296        // Test finding task when cursor is inside function body
20297        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20298            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20299        });
20300        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20301        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20302
20303        // Test finding task when cursor is on function name
20304        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20305            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20306        });
20307        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20308        assert_eq!(row, 8, "Should find task when cursor is on function name");
20309    });
20310}
20311
20312#[gpui::test]
20313async fn test_folding_buffers(cx: &mut TestAppContext) {
20314    init_test(cx, |_| {});
20315
20316    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20317    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20318    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20319
20320    let fs = FakeFs::new(cx.executor());
20321    fs.insert_tree(
20322        path!("/a"),
20323        json!({
20324            "first.rs": sample_text_1,
20325            "second.rs": sample_text_2,
20326            "third.rs": sample_text_3,
20327        }),
20328    )
20329    .await;
20330    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20331    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20332    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20333    let worktree = project.update(cx, |project, cx| {
20334        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20335        assert_eq!(worktrees.len(), 1);
20336        worktrees.pop().unwrap()
20337    });
20338    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20339
20340    let buffer_1 = project
20341        .update(cx, |project, cx| {
20342            project.open_buffer((worktree_id, "first.rs"), cx)
20343        })
20344        .await
20345        .unwrap();
20346    let buffer_2 = project
20347        .update(cx, |project, cx| {
20348            project.open_buffer((worktree_id, "second.rs"), cx)
20349        })
20350        .await
20351        .unwrap();
20352    let buffer_3 = project
20353        .update(cx, |project, cx| {
20354            project.open_buffer((worktree_id, "third.rs"), cx)
20355        })
20356        .await
20357        .unwrap();
20358
20359    let multi_buffer = cx.new(|cx| {
20360        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20361        multi_buffer.push_excerpts(
20362            buffer_1.clone(),
20363            [
20364                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20365                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20366                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20367            ],
20368            cx,
20369        );
20370        multi_buffer.push_excerpts(
20371            buffer_2.clone(),
20372            [
20373                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20374                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20375                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20376            ],
20377            cx,
20378        );
20379        multi_buffer.push_excerpts(
20380            buffer_3.clone(),
20381            [
20382                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20383                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20384                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20385            ],
20386            cx,
20387        );
20388        multi_buffer
20389    });
20390    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20391        Editor::new(
20392            EditorMode::full(),
20393            multi_buffer.clone(),
20394            Some(project.clone()),
20395            window,
20396            cx,
20397        )
20398    });
20399
20400    assert_eq!(
20401        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20402        "\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",
20403    );
20404
20405    multi_buffer_editor.update(cx, |editor, cx| {
20406        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20407    });
20408    assert_eq!(
20409        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20410        "\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",
20411        "After folding the first buffer, its text should not be displayed"
20412    );
20413
20414    multi_buffer_editor.update(cx, |editor, cx| {
20415        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20416    });
20417    assert_eq!(
20418        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20419        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20420        "After folding the second buffer, its text should not be displayed"
20421    );
20422
20423    multi_buffer_editor.update(cx, |editor, cx| {
20424        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20425    });
20426    assert_eq!(
20427        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20428        "\n\n\n\n\n",
20429        "After folding the third buffer, its text should not be displayed"
20430    );
20431
20432    // Emulate selection inside the fold logic, that should work
20433    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20434        editor
20435            .snapshot(window, cx)
20436            .next_line_boundary(Point::new(0, 4));
20437    });
20438
20439    multi_buffer_editor.update(cx, |editor, cx| {
20440        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20441    });
20442    assert_eq!(
20443        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20444        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20445        "After unfolding the second buffer, its text should be displayed"
20446    );
20447
20448    // Typing inside of buffer 1 causes that buffer to be unfolded.
20449    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20450        assert_eq!(
20451            multi_buffer
20452                .read(cx)
20453                .snapshot(cx)
20454                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20455                .collect::<String>(),
20456            "bbbb"
20457        );
20458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20459            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20460        });
20461        editor.handle_input("B", window, cx);
20462    });
20463
20464    assert_eq!(
20465        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20466        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20467        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20468    );
20469
20470    multi_buffer_editor.update(cx, |editor, cx| {
20471        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20472    });
20473    assert_eq!(
20474        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20475        "\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",
20476        "After unfolding the all buffers, all original text should be displayed"
20477    );
20478}
20479
20480#[gpui::test]
20481async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20482    init_test(cx, |_| {});
20483
20484    let sample_text_1 = "1111\n2222\n3333".to_string();
20485    let sample_text_2 = "4444\n5555\n6666".to_string();
20486    let sample_text_3 = "7777\n8888\n9999".to_string();
20487
20488    let fs = FakeFs::new(cx.executor());
20489    fs.insert_tree(
20490        path!("/a"),
20491        json!({
20492            "first.rs": sample_text_1,
20493            "second.rs": sample_text_2,
20494            "third.rs": sample_text_3,
20495        }),
20496    )
20497    .await;
20498    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20499    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20500    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20501    let worktree = project.update(cx, |project, cx| {
20502        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20503        assert_eq!(worktrees.len(), 1);
20504        worktrees.pop().unwrap()
20505    });
20506    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20507
20508    let buffer_1 = project
20509        .update(cx, |project, cx| {
20510            project.open_buffer((worktree_id, "first.rs"), cx)
20511        })
20512        .await
20513        .unwrap();
20514    let buffer_2 = project
20515        .update(cx, |project, cx| {
20516            project.open_buffer((worktree_id, "second.rs"), cx)
20517        })
20518        .await
20519        .unwrap();
20520    let buffer_3 = project
20521        .update(cx, |project, cx| {
20522            project.open_buffer((worktree_id, "third.rs"), cx)
20523        })
20524        .await
20525        .unwrap();
20526
20527    let multi_buffer = cx.new(|cx| {
20528        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20529        multi_buffer.push_excerpts(
20530            buffer_1.clone(),
20531            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20532            cx,
20533        );
20534        multi_buffer.push_excerpts(
20535            buffer_2.clone(),
20536            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20537            cx,
20538        );
20539        multi_buffer.push_excerpts(
20540            buffer_3.clone(),
20541            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20542            cx,
20543        );
20544        multi_buffer
20545    });
20546
20547    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20548        Editor::new(
20549            EditorMode::full(),
20550            multi_buffer,
20551            Some(project.clone()),
20552            window,
20553            cx,
20554        )
20555    });
20556
20557    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20558    assert_eq!(
20559        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20560        full_text,
20561    );
20562
20563    multi_buffer_editor.update(cx, |editor, cx| {
20564        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20565    });
20566    assert_eq!(
20567        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20568        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20569        "After folding the first buffer, its text should not be displayed"
20570    );
20571
20572    multi_buffer_editor.update(cx, |editor, cx| {
20573        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20574    });
20575
20576    assert_eq!(
20577        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20578        "\n\n\n\n\n\n7777\n8888\n9999",
20579        "After folding the second buffer, its text should not be displayed"
20580    );
20581
20582    multi_buffer_editor.update(cx, |editor, cx| {
20583        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20584    });
20585    assert_eq!(
20586        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20587        "\n\n\n\n\n",
20588        "After folding the third buffer, its text should not be displayed"
20589    );
20590
20591    multi_buffer_editor.update(cx, |editor, cx| {
20592        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20593    });
20594    assert_eq!(
20595        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20596        "\n\n\n\n4444\n5555\n6666\n\n",
20597        "After unfolding the second buffer, its text should be displayed"
20598    );
20599
20600    multi_buffer_editor.update(cx, |editor, cx| {
20601        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20602    });
20603    assert_eq!(
20604        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20605        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20606        "After unfolding the first buffer, its text should be displayed"
20607    );
20608
20609    multi_buffer_editor.update(cx, |editor, cx| {
20610        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20611    });
20612    assert_eq!(
20613        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20614        full_text,
20615        "After unfolding all buffers, all original text should be displayed"
20616    );
20617}
20618
20619#[gpui::test]
20620async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20621    init_test(cx, |_| {});
20622
20623    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20624
20625    let fs = FakeFs::new(cx.executor());
20626    fs.insert_tree(
20627        path!("/a"),
20628        json!({
20629            "main.rs": sample_text,
20630        }),
20631    )
20632    .await;
20633    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20634    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20635    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20636    let worktree = project.update(cx, |project, cx| {
20637        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20638        assert_eq!(worktrees.len(), 1);
20639        worktrees.pop().unwrap()
20640    });
20641    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20642
20643    let buffer_1 = project
20644        .update(cx, |project, cx| {
20645            project.open_buffer((worktree_id, "main.rs"), cx)
20646        })
20647        .await
20648        .unwrap();
20649
20650    let multi_buffer = cx.new(|cx| {
20651        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20652        multi_buffer.push_excerpts(
20653            buffer_1.clone(),
20654            [ExcerptRange::new(
20655                Point::new(0, 0)
20656                    ..Point::new(
20657                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20658                        0,
20659                    ),
20660            )],
20661            cx,
20662        );
20663        multi_buffer
20664    });
20665    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20666        Editor::new(
20667            EditorMode::full(),
20668            multi_buffer,
20669            Some(project.clone()),
20670            window,
20671            cx,
20672        )
20673    });
20674
20675    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20676    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20677        enum TestHighlight {}
20678        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20679        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20680        editor.highlight_text::<TestHighlight>(
20681            vec![highlight_range.clone()],
20682            HighlightStyle::color(Hsla::green()),
20683            cx,
20684        );
20685        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20686            s.select_ranges(Some(highlight_range))
20687        });
20688    });
20689
20690    let full_text = format!("\n\n{sample_text}");
20691    assert_eq!(
20692        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20693        full_text,
20694    );
20695}
20696
20697#[gpui::test]
20698async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20699    init_test(cx, |_| {});
20700    cx.update(|cx| {
20701        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20702            "keymaps/default-linux.json",
20703            cx,
20704        )
20705        .unwrap();
20706        cx.bind_keys(default_key_bindings);
20707    });
20708
20709    let (editor, cx) = cx.add_window_view(|window, cx| {
20710        let multi_buffer = MultiBuffer::build_multi(
20711            [
20712                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20713                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20714                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20715                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20716            ],
20717            cx,
20718        );
20719        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20720
20721        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20722        // fold all but the second buffer, so that we test navigating between two
20723        // adjacent folded buffers, as well as folded buffers at the start and
20724        // end the multibuffer
20725        editor.fold_buffer(buffer_ids[0], cx);
20726        editor.fold_buffer(buffer_ids[2], cx);
20727        editor.fold_buffer(buffer_ids[3], cx);
20728
20729        editor
20730    });
20731    cx.simulate_resize(size(px(1000.), px(1000.)));
20732
20733    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20734    cx.assert_excerpts_with_selections(indoc! {"
20735        [EXCERPT]
20736        ˇ[FOLDED]
20737        [EXCERPT]
20738        a1
20739        b1
20740        [EXCERPT]
20741        [FOLDED]
20742        [EXCERPT]
20743        [FOLDED]
20744        "
20745    });
20746    cx.simulate_keystroke("down");
20747    cx.assert_excerpts_with_selections(indoc! {"
20748        [EXCERPT]
20749        [FOLDED]
20750        [EXCERPT]
20751        ˇa1
20752        b1
20753        [EXCERPT]
20754        [FOLDED]
20755        [EXCERPT]
20756        [FOLDED]
20757        "
20758    });
20759    cx.simulate_keystroke("down");
20760    cx.assert_excerpts_with_selections(indoc! {"
20761        [EXCERPT]
20762        [FOLDED]
20763        [EXCERPT]
20764        a1
20765        ˇb1
20766        [EXCERPT]
20767        [FOLDED]
20768        [EXCERPT]
20769        [FOLDED]
20770        "
20771    });
20772    cx.simulate_keystroke("down");
20773    cx.assert_excerpts_with_selections(indoc! {"
20774        [EXCERPT]
20775        [FOLDED]
20776        [EXCERPT]
20777        a1
20778        b1
20779        ˇ[EXCERPT]
20780        [FOLDED]
20781        [EXCERPT]
20782        [FOLDED]
20783        "
20784    });
20785    cx.simulate_keystroke("down");
20786    cx.assert_excerpts_with_selections(indoc! {"
20787        [EXCERPT]
20788        [FOLDED]
20789        [EXCERPT]
20790        a1
20791        b1
20792        [EXCERPT]
20793        ˇ[FOLDED]
20794        [EXCERPT]
20795        [FOLDED]
20796        "
20797    });
20798    for _ in 0..5 {
20799        cx.simulate_keystroke("down");
20800        cx.assert_excerpts_with_selections(indoc! {"
20801            [EXCERPT]
20802            [FOLDED]
20803            [EXCERPT]
20804            a1
20805            b1
20806            [EXCERPT]
20807            [FOLDED]
20808            [EXCERPT]
20809            ˇ[FOLDED]
20810            "
20811        });
20812    }
20813
20814    cx.simulate_keystroke("up");
20815    cx.assert_excerpts_with_selections(indoc! {"
20816        [EXCERPT]
20817        [FOLDED]
20818        [EXCERPT]
20819        a1
20820        b1
20821        [EXCERPT]
20822        ˇ[FOLDED]
20823        [EXCERPT]
20824        [FOLDED]
20825        "
20826    });
20827    cx.simulate_keystroke("up");
20828    cx.assert_excerpts_with_selections(indoc! {"
20829        [EXCERPT]
20830        [FOLDED]
20831        [EXCERPT]
20832        a1
20833        b1
20834        ˇ[EXCERPT]
20835        [FOLDED]
20836        [EXCERPT]
20837        [FOLDED]
20838        "
20839    });
20840    cx.simulate_keystroke("up");
20841    cx.assert_excerpts_with_selections(indoc! {"
20842        [EXCERPT]
20843        [FOLDED]
20844        [EXCERPT]
20845        a1
20846        ˇb1
20847        [EXCERPT]
20848        [FOLDED]
20849        [EXCERPT]
20850        [FOLDED]
20851        "
20852    });
20853    cx.simulate_keystroke("up");
20854    cx.assert_excerpts_with_selections(indoc! {"
20855        [EXCERPT]
20856        [FOLDED]
20857        [EXCERPT]
20858        ˇa1
20859        b1
20860        [EXCERPT]
20861        [FOLDED]
20862        [EXCERPT]
20863        [FOLDED]
20864        "
20865    });
20866    for _ in 0..5 {
20867        cx.simulate_keystroke("up");
20868        cx.assert_excerpts_with_selections(indoc! {"
20869            [EXCERPT]
20870            ˇ[FOLDED]
20871            [EXCERPT]
20872            a1
20873            b1
20874            [EXCERPT]
20875            [FOLDED]
20876            [EXCERPT]
20877            [FOLDED]
20878            "
20879        });
20880    }
20881}
20882
20883#[gpui::test]
20884async fn test_edit_prediction_text(cx: &mut TestAppContext) {
20885    init_test(cx, |_| {});
20886
20887    // Simple insertion
20888    assert_highlighted_edits(
20889        "Hello, world!",
20890        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
20891        true,
20892        cx,
20893        |highlighted_edits, cx| {
20894            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
20895            assert_eq!(highlighted_edits.highlights.len(), 1);
20896            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
20897            assert_eq!(
20898                highlighted_edits.highlights[0].1.background_color,
20899                Some(cx.theme().status().created_background)
20900            );
20901        },
20902    )
20903    .await;
20904
20905    // Replacement
20906    assert_highlighted_edits(
20907        "This is a test.",
20908        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
20909        false,
20910        cx,
20911        |highlighted_edits, cx| {
20912            assert_eq!(highlighted_edits.text, "That is a test.");
20913            assert_eq!(highlighted_edits.highlights.len(), 1);
20914            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
20915            assert_eq!(
20916                highlighted_edits.highlights[0].1.background_color,
20917                Some(cx.theme().status().created_background)
20918            );
20919        },
20920    )
20921    .await;
20922
20923    // Multiple edits
20924    assert_highlighted_edits(
20925        "Hello, world!",
20926        vec![
20927            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
20928            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
20929        ],
20930        false,
20931        cx,
20932        |highlighted_edits, cx| {
20933            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
20934            assert_eq!(highlighted_edits.highlights.len(), 2);
20935            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
20936            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
20937            assert_eq!(
20938                highlighted_edits.highlights[0].1.background_color,
20939                Some(cx.theme().status().created_background)
20940            );
20941            assert_eq!(
20942                highlighted_edits.highlights[1].1.background_color,
20943                Some(cx.theme().status().created_background)
20944            );
20945        },
20946    )
20947    .await;
20948
20949    // Multiple lines with edits
20950    assert_highlighted_edits(
20951        "First line\nSecond line\nThird line\nFourth line",
20952        vec![
20953            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
20954            (
20955                Point::new(2, 0)..Point::new(2, 10),
20956                "New third line".to_string(),
20957            ),
20958            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
20959        ],
20960        false,
20961        cx,
20962        |highlighted_edits, cx| {
20963            assert_eq!(
20964                highlighted_edits.text,
20965                "Second modified\nNew third line\nFourth updated line"
20966            );
20967            assert_eq!(highlighted_edits.highlights.len(), 3);
20968            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
20969            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
20970            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
20971            for highlight in &highlighted_edits.highlights {
20972                assert_eq!(
20973                    highlight.1.background_color,
20974                    Some(cx.theme().status().created_background)
20975                );
20976            }
20977        },
20978    )
20979    .await;
20980}
20981
20982#[gpui::test]
20983async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
20984    init_test(cx, |_| {});
20985
20986    // Deletion
20987    assert_highlighted_edits(
20988        "Hello, world!",
20989        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
20990        true,
20991        cx,
20992        |highlighted_edits, cx| {
20993            assert_eq!(highlighted_edits.text, "Hello, world!");
20994            assert_eq!(highlighted_edits.highlights.len(), 1);
20995            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
20996            assert_eq!(
20997                highlighted_edits.highlights[0].1.background_color,
20998                Some(cx.theme().status().deleted_background)
20999            );
21000        },
21001    )
21002    .await;
21003
21004    // Insertion
21005    assert_highlighted_edits(
21006        "Hello, world!",
21007        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21008        true,
21009        cx,
21010        |highlighted_edits, cx| {
21011            assert_eq!(highlighted_edits.highlights.len(), 1);
21012            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21013            assert_eq!(
21014                highlighted_edits.highlights[0].1.background_color,
21015                Some(cx.theme().status().created_background)
21016            );
21017        },
21018    )
21019    .await;
21020}
21021
21022async fn assert_highlighted_edits(
21023    text: &str,
21024    edits: Vec<(Range<Point>, String)>,
21025    include_deletions: bool,
21026    cx: &mut TestAppContext,
21027    assertion_fn: impl Fn(HighlightedText, &App),
21028) {
21029    let window = cx.add_window(|window, cx| {
21030        let buffer = MultiBuffer::build_simple(text, cx);
21031        Editor::new(EditorMode::full(), buffer, None, window, cx)
21032    });
21033    let cx = &mut VisualTestContext::from_window(*window, cx);
21034
21035    let (buffer, snapshot) = window
21036        .update(cx, |editor, _window, cx| {
21037            (
21038                editor.buffer().clone(),
21039                editor.buffer().read(cx).snapshot(cx),
21040            )
21041        })
21042        .unwrap();
21043
21044    let edits = edits
21045        .into_iter()
21046        .map(|(range, edit)| {
21047            (
21048                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21049                edit,
21050            )
21051        })
21052        .collect::<Vec<_>>();
21053
21054    let text_anchor_edits = edits
21055        .clone()
21056        .into_iter()
21057        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21058        .collect::<Vec<_>>();
21059
21060    let edit_preview = window
21061        .update(cx, |_, _window, cx| {
21062            buffer
21063                .read(cx)
21064                .as_singleton()
21065                .unwrap()
21066                .read(cx)
21067                .preview_edits(text_anchor_edits.into(), cx)
21068        })
21069        .unwrap()
21070        .await;
21071
21072    cx.update(|_window, cx| {
21073        let highlighted_edits = edit_prediction_edit_text(
21074            snapshot.as_singleton().unwrap().2,
21075            &edits,
21076            &edit_preview,
21077            include_deletions,
21078            cx,
21079        );
21080        assertion_fn(highlighted_edits, cx)
21081    });
21082}
21083
21084#[track_caller]
21085fn assert_breakpoint(
21086    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21087    path: &Arc<Path>,
21088    expected: Vec<(u32, Breakpoint)>,
21089) {
21090    if expected.is_empty() {
21091        assert!(!breakpoints.contains_key(path), "{}", path.display());
21092    } else {
21093        let mut breakpoint = breakpoints
21094            .get(path)
21095            .unwrap()
21096            .iter()
21097            .map(|breakpoint| {
21098                (
21099                    breakpoint.row,
21100                    Breakpoint {
21101                        message: breakpoint.message.clone(),
21102                        state: breakpoint.state,
21103                        condition: breakpoint.condition.clone(),
21104                        hit_condition: breakpoint.hit_condition.clone(),
21105                    },
21106                )
21107            })
21108            .collect::<Vec<_>>();
21109
21110        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21111
21112        assert_eq!(expected, breakpoint);
21113    }
21114}
21115
21116fn add_log_breakpoint_at_cursor(
21117    editor: &mut Editor,
21118    log_message: &str,
21119    window: &mut Window,
21120    cx: &mut Context<Editor>,
21121) {
21122    let (anchor, bp) = editor
21123        .breakpoints_at_cursors(window, cx)
21124        .first()
21125        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21126        .unwrap_or_else(|| {
21127            let cursor_position: Point = editor.selections.newest(cx).head();
21128
21129            let breakpoint_position = editor
21130                .snapshot(window, cx)
21131                .display_snapshot
21132                .buffer_snapshot
21133                .anchor_before(Point::new(cursor_position.row, 0));
21134
21135            (breakpoint_position, Breakpoint::new_log(log_message))
21136        });
21137
21138    editor.edit_breakpoint_at_anchor(
21139        anchor,
21140        bp,
21141        BreakpointEditAction::EditLogMessage(log_message.into()),
21142        cx,
21143    );
21144}
21145
21146#[gpui::test]
21147async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21148    init_test(cx, |_| {});
21149
21150    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21151    let fs = FakeFs::new(cx.executor());
21152    fs.insert_tree(
21153        path!("/a"),
21154        json!({
21155            "main.rs": sample_text,
21156        }),
21157    )
21158    .await;
21159    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21160    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21161    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21162
21163    let fs = FakeFs::new(cx.executor());
21164    fs.insert_tree(
21165        path!("/a"),
21166        json!({
21167            "main.rs": sample_text,
21168        }),
21169    )
21170    .await;
21171    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21172    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21173    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21174    let worktree_id = workspace
21175        .update(cx, |workspace, _window, cx| {
21176            workspace.project().update(cx, |project, cx| {
21177                project.worktrees(cx).next().unwrap().read(cx).id()
21178            })
21179        })
21180        .unwrap();
21181
21182    let buffer = project
21183        .update(cx, |project, cx| {
21184            project.open_buffer((worktree_id, "main.rs"), cx)
21185        })
21186        .await
21187        .unwrap();
21188
21189    let (editor, cx) = cx.add_window_view(|window, cx| {
21190        Editor::new(
21191            EditorMode::full(),
21192            MultiBuffer::build_from_buffer(buffer, cx),
21193            Some(project.clone()),
21194            window,
21195            cx,
21196        )
21197    });
21198
21199    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21200    let abs_path = project.read_with(cx, |project, cx| {
21201        project
21202            .absolute_path(&project_path, cx)
21203            .map(Arc::from)
21204            .unwrap()
21205    });
21206
21207    // assert we can add breakpoint on the first line
21208    editor.update_in(cx, |editor, window, cx| {
21209        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21210        editor.move_to_end(&MoveToEnd, window, cx);
21211        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21212    });
21213
21214    let breakpoints = editor.update(cx, |editor, cx| {
21215        editor
21216            .breakpoint_store()
21217            .as_ref()
21218            .unwrap()
21219            .read(cx)
21220            .all_source_breakpoints(cx)
21221    });
21222
21223    assert_eq!(1, breakpoints.len());
21224    assert_breakpoint(
21225        &breakpoints,
21226        &abs_path,
21227        vec![
21228            (0, Breakpoint::new_standard()),
21229            (3, Breakpoint::new_standard()),
21230        ],
21231    );
21232
21233    editor.update_in(cx, |editor, window, cx| {
21234        editor.move_to_beginning(&MoveToBeginning, window, cx);
21235        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21236    });
21237
21238    let breakpoints = editor.update(cx, |editor, cx| {
21239        editor
21240            .breakpoint_store()
21241            .as_ref()
21242            .unwrap()
21243            .read(cx)
21244            .all_source_breakpoints(cx)
21245    });
21246
21247    assert_eq!(1, breakpoints.len());
21248    assert_breakpoint(
21249        &breakpoints,
21250        &abs_path,
21251        vec![(3, Breakpoint::new_standard())],
21252    );
21253
21254    editor.update_in(cx, |editor, window, cx| {
21255        editor.move_to_end(&MoveToEnd, window, cx);
21256        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21257    });
21258
21259    let breakpoints = editor.update(cx, |editor, cx| {
21260        editor
21261            .breakpoint_store()
21262            .as_ref()
21263            .unwrap()
21264            .read(cx)
21265            .all_source_breakpoints(cx)
21266    });
21267
21268    assert_eq!(0, breakpoints.len());
21269    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21270}
21271
21272#[gpui::test]
21273async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21274    init_test(cx, |_| {});
21275
21276    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21277
21278    let fs = FakeFs::new(cx.executor());
21279    fs.insert_tree(
21280        path!("/a"),
21281        json!({
21282            "main.rs": sample_text,
21283        }),
21284    )
21285    .await;
21286    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21287    let (workspace, cx) =
21288        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21289
21290    let worktree_id = workspace.update(cx, |workspace, cx| {
21291        workspace.project().update(cx, |project, cx| {
21292            project.worktrees(cx).next().unwrap().read(cx).id()
21293        })
21294    });
21295
21296    let buffer = project
21297        .update(cx, |project, cx| {
21298            project.open_buffer((worktree_id, "main.rs"), cx)
21299        })
21300        .await
21301        .unwrap();
21302
21303    let (editor, cx) = cx.add_window_view(|window, cx| {
21304        Editor::new(
21305            EditorMode::full(),
21306            MultiBuffer::build_from_buffer(buffer, cx),
21307            Some(project.clone()),
21308            window,
21309            cx,
21310        )
21311    });
21312
21313    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21314    let abs_path = project.read_with(cx, |project, cx| {
21315        project
21316            .absolute_path(&project_path, cx)
21317            .map(Arc::from)
21318            .unwrap()
21319    });
21320
21321    editor.update_in(cx, |editor, window, cx| {
21322        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21323    });
21324
21325    let breakpoints = editor.update(cx, |editor, cx| {
21326        editor
21327            .breakpoint_store()
21328            .as_ref()
21329            .unwrap()
21330            .read(cx)
21331            .all_source_breakpoints(cx)
21332    });
21333
21334    assert_breakpoint(
21335        &breakpoints,
21336        &abs_path,
21337        vec![(0, Breakpoint::new_log("hello world"))],
21338    );
21339
21340    // Removing a log message from a log breakpoint should remove it
21341    editor.update_in(cx, |editor, window, cx| {
21342        add_log_breakpoint_at_cursor(editor, "", window, cx);
21343    });
21344
21345    let breakpoints = editor.update(cx, |editor, cx| {
21346        editor
21347            .breakpoint_store()
21348            .as_ref()
21349            .unwrap()
21350            .read(cx)
21351            .all_source_breakpoints(cx)
21352    });
21353
21354    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21355
21356    editor.update_in(cx, |editor, window, cx| {
21357        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21358        editor.move_to_end(&MoveToEnd, window, cx);
21359        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21360        // Not adding a log message to a standard breakpoint shouldn't remove it
21361        add_log_breakpoint_at_cursor(editor, "", window, cx);
21362    });
21363
21364    let breakpoints = editor.update(cx, |editor, cx| {
21365        editor
21366            .breakpoint_store()
21367            .as_ref()
21368            .unwrap()
21369            .read(cx)
21370            .all_source_breakpoints(cx)
21371    });
21372
21373    assert_breakpoint(
21374        &breakpoints,
21375        &abs_path,
21376        vec![
21377            (0, Breakpoint::new_standard()),
21378            (3, Breakpoint::new_standard()),
21379        ],
21380    );
21381
21382    editor.update_in(cx, |editor, window, cx| {
21383        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21384    });
21385
21386    let breakpoints = editor.update(cx, |editor, cx| {
21387        editor
21388            .breakpoint_store()
21389            .as_ref()
21390            .unwrap()
21391            .read(cx)
21392            .all_source_breakpoints(cx)
21393    });
21394
21395    assert_breakpoint(
21396        &breakpoints,
21397        &abs_path,
21398        vec![
21399            (0, Breakpoint::new_standard()),
21400            (3, Breakpoint::new_log("hello world")),
21401        ],
21402    );
21403
21404    editor.update_in(cx, |editor, window, cx| {
21405        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21406    });
21407
21408    let breakpoints = editor.update(cx, |editor, cx| {
21409        editor
21410            .breakpoint_store()
21411            .as_ref()
21412            .unwrap()
21413            .read(cx)
21414            .all_source_breakpoints(cx)
21415    });
21416
21417    assert_breakpoint(
21418        &breakpoints,
21419        &abs_path,
21420        vec![
21421            (0, Breakpoint::new_standard()),
21422            (3, Breakpoint::new_log("hello Earth!!")),
21423        ],
21424    );
21425}
21426
21427/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21428/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21429/// or when breakpoints were placed out of order. This tests for a regression too
21430#[gpui::test]
21431async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21432    init_test(cx, |_| {});
21433
21434    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21435    let fs = FakeFs::new(cx.executor());
21436    fs.insert_tree(
21437        path!("/a"),
21438        json!({
21439            "main.rs": sample_text,
21440        }),
21441    )
21442    .await;
21443    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21444    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21445    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21446
21447    let fs = FakeFs::new(cx.executor());
21448    fs.insert_tree(
21449        path!("/a"),
21450        json!({
21451            "main.rs": sample_text,
21452        }),
21453    )
21454    .await;
21455    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21456    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21457    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21458    let worktree_id = workspace
21459        .update(cx, |workspace, _window, cx| {
21460            workspace.project().update(cx, |project, cx| {
21461                project.worktrees(cx).next().unwrap().read(cx).id()
21462            })
21463        })
21464        .unwrap();
21465
21466    let buffer = project
21467        .update(cx, |project, cx| {
21468            project.open_buffer((worktree_id, "main.rs"), cx)
21469        })
21470        .await
21471        .unwrap();
21472
21473    let (editor, cx) = cx.add_window_view(|window, cx| {
21474        Editor::new(
21475            EditorMode::full(),
21476            MultiBuffer::build_from_buffer(buffer, cx),
21477            Some(project.clone()),
21478            window,
21479            cx,
21480        )
21481    });
21482
21483    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21484    let abs_path = project.read_with(cx, |project, cx| {
21485        project
21486            .absolute_path(&project_path, cx)
21487            .map(Arc::from)
21488            .unwrap()
21489    });
21490
21491    // assert we can add breakpoint on the first line
21492    editor.update_in(cx, |editor, window, cx| {
21493        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21494        editor.move_to_end(&MoveToEnd, window, cx);
21495        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21496        editor.move_up(&MoveUp, window, cx);
21497        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21498    });
21499
21500    let breakpoints = editor.update(cx, |editor, cx| {
21501        editor
21502            .breakpoint_store()
21503            .as_ref()
21504            .unwrap()
21505            .read(cx)
21506            .all_source_breakpoints(cx)
21507    });
21508
21509    assert_eq!(1, breakpoints.len());
21510    assert_breakpoint(
21511        &breakpoints,
21512        &abs_path,
21513        vec![
21514            (0, Breakpoint::new_standard()),
21515            (2, Breakpoint::new_standard()),
21516            (3, Breakpoint::new_standard()),
21517        ],
21518    );
21519
21520    editor.update_in(cx, |editor, window, cx| {
21521        editor.move_to_beginning(&MoveToBeginning, window, cx);
21522        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21523        editor.move_to_end(&MoveToEnd, window, cx);
21524        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21525        // Disabling a breakpoint that doesn't exist should do nothing
21526        editor.move_up(&MoveUp, window, cx);
21527        editor.move_up(&MoveUp, window, cx);
21528        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21529    });
21530
21531    let breakpoints = editor.update(cx, |editor, cx| {
21532        editor
21533            .breakpoint_store()
21534            .as_ref()
21535            .unwrap()
21536            .read(cx)
21537            .all_source_breakpoints(cx)
21538    });
21539
21540    let disable_breakpoint = {
21541        let mut bp = Breakpoint::new_standard();
21542        bp.state = BreakpointState::Disabled;
21543        bp
21544    };
21545
21546    assert_eq!(1, breakpoints.len());
21547    assert_breakpoint(
21548        &breakpoints,
21549        &abs_path,
21550        vec![
21551            (0, disable_breakpoint.clone()),
21552            (2, Breakpoint::new_standard()),
21553            (3, disable_breakpoint.clone()),
21554        ],
21555    );
21556
21557    editor.update_in(cx, |editor, window, cx| {
21558        editor.move_to_beginning(&MoveToBeginning, window, cx);
21559        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21560        editor.move_to_end(&MoveToEnd, window, cx);
21561        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21562        editor.move_up(&MoveUp, window, cx);
21563        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21564    });
21565
21566    let breakpoints = editor.update(cx, |editor, cx| {
21567        editor
21568            .breakpoint_store()
21569            .as_ref()
21570            .unwrap()
21571            .read(cx)
21572            .all_source_breakpoints(cx)
21573    });
21574
21575    assert_eq!(1, breakpoints.len());
21576    assert_breakpoint(
21577        &breakpoints,
21578        &abs_path,
21579        vec![
21580            (0, Breakpoint::new_standard()),
21581            (2, disable_breakpoint),
21582            (3, Breakpoint::new_standard()),
21583        ],
21584    );
21585}
21586
21587#[gpui::test]
21588async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21589    init_test(cx, |_| {});
21590    let capabilities = lsp::ServerCapabilities {
21591        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21592            prepare_provider: Some(true),
21593            work_done_progress_options: Default::default(),
21594        })),
21595        ..Default::default()
21596    };
21597    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21598
21599    cx.set_state(indoc! {"
21600        struct Fˇoo {}
21601    "});
21602
21603    cx.update_editor(|editor, _, cx| {
21604        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21605        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21606        editor.highlight_background::<DocumentHighlightRead>(
21607            &[highlight_range],
21608            |theme| theme.colors().editor_document_highlight_read_background,
21609            cx,
21610        );
21611    });
21612
21613    let mut prepare_rename_handler = cx
21614        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21615            move |_, _, _| async move {
21616                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21617                    start: lsp::Position {
21618                        line: 0,
21619                        character: 7,
21620                    },
21621                    end: lsp::Position {
21622                        line: 0,
21623                        character: 10,
21624                    },
21625                })))
21626            },
21627        );
21628    let prepare_rename_task = cx
21629        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21630        .expect("Prepare rename was not started");
21631    prepare_rename_handler.next().await.unwrap();
21632    prepare_rename_task.await.expect("Prepare rename failed");
21633
21634    let mut rename_handler =
21635        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21636            let edit = lsp::TextEdit {
21637                range: lsp::Range {
21638                    start: lsp::Position {
21639                        line: 0,
21640                        character: 7,
21641                    },
21642                    end: lsp::Position {
21643                        line: 0,
21644                        character: 10,
21645                    },
21646                },
21647                new_text: "FooRenamed".to_string(),
21648            };
21649            Ok(Some(lsp::WorkspaceEdit::new(
21650                // Specify the same edit twice
21651                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21652            )))
21653        });
21654    let rename_task = cx
21655        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21656        .expect("Confirm rename was not started");
21657    rename_handler.next().await.unwrap();
21658    rename_task.await.expect("Confirm rename failed");
21659    cx.run_until_parked();
21660
21661    // Despite two edits, only one is actually applied as those are identical
21662    cx.assert_editor_state(indoc! {"
21663        struct FooRenamedˇ {}
21664    "});
21665}
21666
21667#[gpui::test]
21668async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21669    init_test(cx, |_| {});
21670    // These capabilities indicate that the server does not support prepare rename.
21671    let capabilities = lsp::ServerCapabilities {
21672        rename_provider: Some(lsp::OneOf::Left(true)),
21673        ..Default::default()
21674    };
21675    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21676
21677    cx.set_state(indoc! {"
21678        struct Fˇoo {}
21679    "});
21680
21681    cx.update_editor(|editor, _window, cx| {
21682        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21683        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21684        editor.highlight_background::<DocumentHighlightRead>(
21685            &[highlight_range],
21686            |theme| theme.colors().editor_document_highlight_read_background,
21687            cx,
21688        );
21689    });
21690
21691    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21692        .expect("Prepare rename was not started")
21693        .await
21694        .expect("Prepare rename failed");
21695
21696    let mut rename_handler =
21697        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21698            let edit = lsp::TextEdit {
21699                range: lsp::Range {
21700                    start: lsp::Position {
21701                        line: 0,
21702                        character: 7,
21703                    },
21704                    end: lsp::Position {
21705                        line: 0,
21706                        character: 10,
21707                    },
21708                },
21709                new_text: "FooRenamed".to_string(),
21710            };
21711            Ok(Some(lsp::WorkspaceEdit::new(
21712                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21713            )))
21714        });
21715    let rename_task = cx
21716        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21717        .expect("Confirm rename was not started");
21718    rename_handler.next().await.unwrap();
21719    rename_task.await.expect("Confirm rename failed");
21720    cx.run_until_parked();
21721
21722    // Correct range is renamed, as `surrounding_word` is used to find it.
21723    cx.assert_editor_state(indoc! {"
21724        struct FooRenamedˇ {}
21725    "});
21726}
21727
21728#[gpui::test]
21729async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21730    init_test(cx, |_| {});
21731    let mut cx = EditorTestContext::new(cx).await;
21732
21733    let language = Arc::new(
21734        Language::new(
21735            LanguageConfig::default(),
21736            Some(tree_sitter_html::LANGUAGE.into()),
21737        )
21738        .with_brackets_query(
21739            r#"
21740            ("<" @open "/>" @close)
21741            ("</" @open ">" @close)
21742            ("<" @open ">" @close)
21743            ("\"" @open "\"" @close)
21744            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21745        "#,
21746        )
21747        .unwrap(),
21748    );
21749    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21750
21751    cx.set_state(indoc! {"
21752        <span>ˇ</span>
21753    "});
21754    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21755    cx.assert_editor_state(indoc! {"
21756        <span>
21757        ˇ
21758        </span>
21759    "});
21760
21761    cx.set_state(indoc! {"
21762        <span><span></span>ˇ</span>
21763    "});
21764    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21765    cx.assert_editor_state(indoc! {"
21766        <span><span></span>
21767        ˇ</span>
21768    "});
21769
21770    cx.set_state(indoc! {"
21771        <span>ˇ
21772        </span>
21773    "});
21774    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21775    cx.assert_editor_state(indoc! {"
21776        <span>
21777        ˇ
21778        </span>
21779    "});
21780}
21781
21782#[gpui::test(iterations = 10)]
21783async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21784    init_test(cx, |_| {});
21785
21786    let fs = FakeFs::new(cx.executor());
21787    fs.insert_tree(
21788        path!("/dir"),
21789        json!({
21790            "a.ts": "a",
21791        }),
21792    )
21793    .await;
21794
21795    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21796    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21797    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21798
21799    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21800    language_registry.add(Arc::new(Language::new(
21801        LanguageConfig {
21802            name: "TypeScript".into(),
21803            matcher: LanguageMatcher {
21804                path_suffixes: vec!["ts".to_string()],
21805                ..Default::default()
21806            },
21807            ..Default::default()
21808        },
21809        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21810    )));
21811    let mut fake_language_servers = language_registry.register_fake_lsp(
21812        "TypeScript",
21813        FakeLspAdapter {
21814            capabilities: lsp::ServerCapabilities {
21815                code_lens_provider: Some(lsp::CodeLensOptions {
21816                    resolve_provider: Some(true),
21817                }),
21818                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21819                    commands: vec!["_the/command".to_string()],
21820                    ..lsp::ExecuteCommandOptions::default()
21821                }),
21822                ..lsp::ServerCapabilities::default()
21823            },
21824            ..FakeLspAdapter::default()
21825        },
21826    );
21827
21828    let editor = workspace
21829        .update(cx, |workspace, window, cx| {
21830            workspace.open_abs_path(
21831                PathBuf::from(path!("/dir/a.ts")),
21832                OpenOptions::default(),
21833                window,
21834                cx,
21835            )
21836        })
21837        .unwrap()
21838        .await
21839        .unwrap()
21840        .downcast::<Editor>()
21841        .unwrap();
21842    cx.executor().run_until_parked();
21843
21844    let fake_server = fake_language_servers.next().await.unwrap();
21845
21846    let buffer = editor.update(cx, |editor, cx| {
21847        editor
21848            .buffer()
21849            .read(cx)
21850            .as_singleton()
21851            .expect("have opened a single file by path")
21852    });
21853
21854    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21855    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21856    drop(buffer_snapshot);
21857    let actions = cx
21858        .update_window(*workspace, |_, window, cx| {
21859            project.code_actions(&buffer, anchor..anchor, window, cx)
21860        })
21861        .unwrap();
21862
21863    fake_server
21864        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21865            Ok(Some(vec![
21866                lsp::CodeLens {
21867                    range: lsp::Range::default(),
21868                    command: Some(lsp::Command {
21869                        title: "Code lens command".to_owned(),
21870                        command: "_the/command".to_owned(),
21871                        arguments: None,
21872                    }),
21873                    data: None,
21874                },
21875                lsp::CodeLens {
21876                    range: lsp::Range::default(),
21877                    command: Some(lsp::Command {
21878                        title: "Command not in capabilities".to_owned(),
21879                        command: "not in capabilities".to_owned(),
21880                        arguments: None,
21881                    }),
21882                    data: None,
21883                },
21884                lsp::CodeLens {
21885                    range: lsp::Range {
21886                        start: lsp::Position {
21887                            line: 1,
21888                            character: 1,
21889                        },
21890                        end: lsp::Position {
21891                            line: 1,
21892                            character: 1,
21893                        },
21894                    },
21895                    command: Some(lsp::Command {
21896                        title: "Command not in range".to_owned(),
21897                        command: "_the/command".to_owned(),
21898                        arguments: None,
21899                    }),
21900                    data: None,
21901                },
21902            ]))
21903        })
21904        .next()
21905        .await;
21906
21907    let actions = actions.await.unwrap();
21908    assert_eq!(
21909        actions.len(),
21910        1,
21911        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
21912    );
21913    let action = actions[0].clone();
21914    let apply = project.update(cx, |project, cx| {
21915        project.apply_code_action(buffer.clone(), action, true, cx)
21916    });
21917
21918    // Resolving the code action does not populate its edits. In absence of
21919    // edits, we must execute the given command.
21920    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
21921        |mut lens, _| async move {
21922            let lens_command = lens.command.as_mut().expect("should have a command");
21923            assert_eq!(lens_command.title, "Code lens command");
21924            lens_command.arguments = Some(vec![json!("the-argument")]);
21925            Ok(lens)
21926        },
21927    );
21928
21929    // While executing the command, the language server sends the editor
21930    // a `workspaceEdit` request.
21931    fake_server
21932        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
21933            let fake = fake_server.clone();
21934            move |params, _| {
21935                assert_eq!(params.command, "_the/command");
21936                let fake = fake.clone();
21937                async move {
21938                    fake.server
21939                        .request::<lsp::request::ApplyWorkspaceEdit>(
21940                            lsp::ApplyWorkspaceEditParams {
21941                                label: None,
21942                                edit: lsp::WorkspaceEdit {
21943                                    changes: Some(
21944                                        [(
21945                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
21946                                            vec![lsp::TextEdit {
21947                                                range: lsp::Range::new(
21948                                                    lsp::Position::new(0, 0),
21949                                                    lsp::Position::new(0, 0),
21950                                                ),
21951                                                new_text: "X".into(),
21952                                            }],
21953                                        )]
21954                                        .into_iter()
21955                                        .collect(),
21956                                    ),
21957                                    ..lsp::WorkspaceEdit::default()
21958                                },
21959                            },
21960                        )
21961                        .await
21962                        .into_response()
21963                        .unwrap();
21964                    Ok(Some(json!(null)))
21965                }
21966            }
21967        })
21968        .next()
21969        .await;
21970
21971    // Applying the code lens command returns a project transaction containing the edits
21972    // sent by the language server in its `workspaceEdit` request.
21973    let transaction = apply.await.unwrap();
21974    assert!(transaction.0.contains_key(&buffer));
21975    buffer.update(cx, |buffer, cx| {
21976        assert_eq!(buffer.text(), "Xa");
21977        buffer.undo(cx);
21978        assert_eq!(buffer.text(), "a");
21979    });
21980
21981    let actions_after_edits = cx
21982        .update_window(*workspace, |_, window, cx| {
21983            project.code_actions(&buffer, anchor..anchor, window, cx)
21984        })
21985        .unwrap()
21986        .await
21987        .unwrap();
21988    assert_eq!(
21989        actions, actions_after_edits,
21990        "For the same selection, same code lens actions should be returned"
21991    );
21992
21993    let _responses =
21994        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21995            panic!("No more code lens requests are expected");
21996        });
21997    editor.update_in(cx, |editor, window, cx| {
21998        editor.select_all(&SelectAll, window, cx);
21999    });
22000    cx.executor().run_until_parked();
22001    let new_actions = cx
22002        .update_window(*workspace, |_, window, cx| {
22003            project.code_actions(&buffer, anchor..anchor, window, cx)
22004        })
22005        .unwrap()
22006        .await
22007        .unwrap();
22008    assert_eq!(
22009        actions, new_actions,
22010        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22011    );
22012}
22013
22014#[gpui::test]
22015async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22016    init_test(cx, |_| {});
22017
22018    let fs = FakeFs::new(cx.executor());
22019    let main_text = r#"fn main() {
22020println!("1");
22021println!("2");
22022println!("3");
22023println!("4");
22024println!("5");
22025}"#;
22026    let lib_text = "mod foo {}";
22027    fs.insert_tree(
22028        path!("/a"),
22029        json!({
22030            "lib.rs": lib_text,
22031            "main.rs": main_text,
22032        }),
22033    )
22034    .await;
22035
22036    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22037    let (workspace, cx) =
22038        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22039    let worktree_id = workspace.update(cx, |workspace, cx| {
22040        workspace.project().update(cx, |project, cx| {
22041            project.worktrees(cx).next().unwrap().read(cx).id()
22042        })
22043    });
22044
22045    let expected_ranges = vec![
22046        Point::new(0, 0)..Point::new(0, 0),
22047        Point::new(1, 0)..Point::new(1, 1),
22048        Point::new(2, 0)..Point::new(2, 2),
22049        Point::new(3, 0)..Point::new(3, 3),
22050    ];
22051
22052    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22053    let editor_1 = workspace
22054        .update_in(cx, |workspace, window, cx| {
22055            workspace.open_path(
22056                (worktree_id, "main.rs"),
22057                Some(pane_1.downgrade()),
22058                true,
22059                window,
22060                cx,
22061            )
22062        })
22063        .unwrap()
22064        .await
22065        .downcast::<Editor>()
22066        .unwrap();
22067    pane_1.update(cx, |pane, cx| {
22068        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22069        open_editor.update(cx, |editor, cx| {
22070            assert_eq!(
22071                editor.display_text(cx),
22072                main_text,
22073                "Original main.rs text on initial open",
22074            );
22075            assert_eq!(
22076                editor
22077                    .selections
22078                    .all::<Point>(cx)
22079                    .into_iter()
22080                    .map(|s| s.range())
22081                    .collect::<Vec<_>>(),
22082                vec![Point::zero()..Point::zero()],
22083                "Default selections on initial open",
22084            );
22085        })
22086    });
22087    editor_1.update_in(cx, |editor, window, cx| {
22088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22089            s.select_ranges(expected_ranges.clone());
22090        });
22091    });
22092
22093    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22094        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22095    });
22096    let editor_2 = workspace
22097        .update_in(cx, |workspace, window, cx| {
22098            workspace.open_path(
22099                (worktree_id, "main.rs"),
22100                Some(pane_2.downgrade()),
22101                true,
22102                window,
22103                cx,
22104            )
22105        })
22106        .unwrap()
22107        .await
22108        .downcast::<Editor>()
22109        .unwrap();
22110    pane_2.update(cx, |pane, cx| {
22111        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22112        open_editor.update(cx, |editor, cx| {
22113            assert_eq!(
22114                editor.display_text(cx),
22115                main_text,
22116                "Original main.rs text on initial open in another panel",
22117            );
22118            assert_eq!(
22119                editor
22120                    .selections
22121                    .all::<Point>(cx)
22122                    .into_iter()
22123                    .map(|s| s.range())
22124                    .collect::<Vec<_>>(),
22125                vec![Point::zero()..Point::zero()],
22126                "Default selections on initial open in another panel",
22127            );
22128        })
22129    });
22130
22131    editor_2.update_in(cx, |editor, window, cx| {
22132        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22133    });
22134
22135    let _other_editor_1 = workspace
22136        .update_in(cx, |workspace, window, cx| {
22137            workspace.open_path(
22138                (worktree_id, "lib.rs"),
22139                Some(pane_1.downgrade()),
22140                true,
22141                window,
22142                cx,
22143            )
22144        })
22145        .unwrap()
22146        .await
22147        .downcast::<Editor>()
22148        .unwrap();
22149    pane_1
22150        .update_in(cx, |pane, window, cx| {
22151            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22152        })
22153        .await
22154        .unwrap();
22155    drop(editor_1);
22156    pane_1.update(cx, |pane, cx| {
22157        pane.active_item()
22158            .unwrap()
22159            .downcast::<Editor>()
22160            .unwrap()
22161            .update(cx, |editor, cx| {
22162                assert_eq!(
22163                    editor.display_text(cx),
22164                    lib_text,
22165                    "Other file should be open and active",
22166                );
22167            });
22168        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22169    });
22170
22171    let _other_editor_2 = workspace
22172        .update_in(cx, |workspace, window, cx| {
22173            workspace.open_path(
22174                (worktree_id, "lib.rs"),
22175                Some(pane_2.downgrade()),
22176                true,
22177                window,
22178                cx,
22179            )
22180        })
22181        .unwrap()
22182        .await
22183        .downcast::<Editor>()
22184        .unwrap();
22185    pane_2
22186        .update_in(cx, |pane, window, cx| {
22187            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22188        })
22189        .await
22190        .unwrap();
22191    drop(editor_2);
22192    pane_2.update(cx, |pane, cx| {
22193        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22194        open_editor.update(cx, |editor, cx| {
22195            assert_eq!(
22196                editor.display_text(cx),
22197                lib_text,
22198                "Other file should be open and active in another panel too",
22199            );
22200        });
22201        assert_eq!(
22202            pane.items().count(),
22203            1,
22204            "No other editors should be open in another pane",
22205        );
22206    });
22207
22208    let _editor_1_reopened = workspace
22209        .update_in(cx, |workspace, window, cx| {
22210            workspace.open_path(
22211                (worktree_id, "main.rs"),
22212                Some(pane_1.downgrade()),
22213                true,
22214                window,
22215                cx,
22216            )
22217        })
22218        .unwrap()
22219        .await
22220        .downcast::<Editor>()
22221        .unwrap();
22222    let _editor_2_reopened = workspace
22223        .update_in(cx, |workspace, window, cx| {
22224            workspace.open_path(
22225                (worktree_id, "main.rs"),
22226                Some(pane_2.downgrade()),
22227                true,
22228                window,
22229                cx,
22230            )
22231        })
22232        .unwrap()
22233        .await
22234        .downcast::<Editor>()
22235        .unwrap();
22236    pane_1.update(cx, |pane, cx| {
22237        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22238        open_editor.update(cx, |editor, cx| {
22239            assert_eq!(
22240                editor.display_text(cx),
22241                main_text,
22242                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22243            );
22244            assert_eq!(
22245                editor
22246                    .selections
22247                    .all::<Point>(cx)
22248                    .into_iter()
22249                    .map(|s| s.range())
22250                    .collect::<Vec<_>>(),
22251                expected_ranges,
22252                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22253            );
22254        })
22255    });
22256    pane_2.update(cx, |pane, cx| {
22257        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22258        open_editor.update(cx, |editor, cx| {
22259            assert_eq!(
22260                editor.display_text(cx),
22261                r#"fn main() {
22262⋯rintln!("1");
22263⋯intln!("2");
22264⋯ntln!("3");
22265println!("4");
22266println!("5");
22267}"#,
22268                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22269            );
22270            assert_eq!(
22271                editor
22272                    .selections
22273                    .all::<Point>(cx)
22274                    .into_iter()
22275                    .map(|s| s.range())
22276                    .collect::<Vec<_>>(),
22277                vec![Point::zero()..Point::zero()],
22278                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22279            );
22280        })
22281    });
22282}
22283
22284#[gpui::test]
22285async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22286    init_test(cx, |_| {});
22287
22288    let fs = FakeFs::new(cx.executor());
22289    let main_text = r#"fn main() {
22290println!("1");
22291println!("2");
22292println!("3");
22293println!("4");
22294println!("5");
22295}"#;
22296    let lib_text = "mod foo {}";
22297    fs.insert_tree(
22298        path!("/a"),
22299        json!({
22300            "lib.rs": lib_text,
22301            "main.rs": main_text,
22302        }),
22303    )
22304    .await;
22305
22306    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22307    let (workspace, cx) =
22308        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22309    let worktree_id = workspace.update(cx, |workspace, cx| {
22310        workspace.project().update(cx, |project, cx| {
22311            project.worktrees(cx).next().unwrap().read(cx).id()
22312        })
22313    });
22314
22315    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22316    let editor = workspace
22317        .update_in(cx, |workspace, window, cx| {
22318            workspace.open_path(
22319                (worktree_id, "main.rs"),
22320                Some(pane.downgrade()),
22321                true,
22322                window,
22323                cx,
22324            )
22325        })
22326        .unwrap()
22327        .await
22328        .downcast::<Editor>()
22329        .unwrap();
22330    pane.update(cx, |pane, cx| {
22331        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22332        open_editor.update(cx, |editor, cx| {
22333            assert_eq!(
22334                editor.display_text(cx),
22335                main_text,
22336                "Original main.rs text on initial open",
22337            );
22338        })
22339    });
22340    editor.update_in(cx, |editor, window, cx| {
22341        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22342    });
22343
22344    cx.update_global(|store: &mut SettingsStore, cx| {
22345        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22346            s.restore_on_file_reopen = Some(false);
22347        });
22348    });
22349    editor.update_in(cx, |editor, window, cx| {
22350        editor.fold_ranges(
22351            vec![
22352                Point::new(1, 0)..Point::new(1, 1),
22353                Point::new(2, 0)..Point::new(2, 2),
22354                Point::new(3, 0)..Point::new(3, 3),
22355            ],
22356            false,
22357            window,
22358            cx,
22359        );
22360    });
22361    pane.update_in(cx, |pane, window, cx| {
22362        pane.close_all_items(&CloseAllItems::default(), window, cx)
22363    })
22364    .await
22365    .unwrap();
22366    pane.update(cx, |pane, _| {
22367        assert!(pane.active_item().is_none());
22368    });
22369    cx.update_global(|store: &mut SettingsStore, cx| {
22370        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22371            s.restore_on_file_reopen = Some(true);
22372        });
22373    });
22374
22375    let _editor_reopened = workspace
22376        .update_in(cx, |workspace, window, cx| {
22377            workspace.open_path(
22378                (worktree_id, "main.rs"),
22379                Some(pane.downgrade()),
22380                true,
22381                window,
22382                cx,
22383            )
22384        })
22385        .unwrap()
22386        .await
22387        .downcast::<Editor>()
22388        .unwrap();
22389    pane.update(cx, |pane, cx| {
22390        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22391        open_editor.update(cx, |editor, cx| {
22392            assert_eq!(
22393                editor.display_text(cx),
22394                main_text,
22395                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22396            );
22397        })
22398    });
22399}
22400
22401#[gpui::test]
22402async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22403    struct EmptyModalView {
22404        focus_handle: gpui::FocusHandle,
22405    }
22406    impl EventEmitter<DismissEvent> for EmptyModalView {}
22407    impl Render for EmptyModalView {
22408        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22409            div()
22410        }
22411    }
22412    impl Focusable for EmptyModalView {
22413        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22414            self.focus_handle.clone()
22415        }
22416    }
22417    impl workspace::ModalView for EmptyModalView {}
22418    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22419        EmptyModalView {
22420            focus_handle: cx.focus_handle(),
22421        }
22422    }
22423
22424    init_test(cx, |_| {});
22425
22426    let fs = FakeFs::new(cx.executor());
22427    let project = Project::test(fs, [], cx).await;
22428    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22429    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22430    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22431    let editor = cx.new_window_entity(|window, cx| {
22432        Editor::new(
22433            EditorMode::full(),
22434            buffer,
22435            Some(project.clone()),
22436            window,
22437            cx,
22438        )
22439    });
22440    workspace
22441        .update(cx, |workspace, window, cx| {
22442            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22443        })
22444        .unwrap();
22445    editor.update_in(cx, |editor, window, cx| {
22446        editor.open_context_menu(&OpenContextMenu, window, cx);
22447        assert!(editor.mouse_context_menu.is_some());
22448    });
22449    workspace
22450        .update(cx, |workspace, window, cx| {
22451            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22452        })
22453        .unwrap();
22454    cx.read(|cx| {
22455        assert!(editor.read(cx).mouse_context_menu.is_none());
22456    });
22457}
22458
22459#[gpui::test]
22460async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22461    init_test(cx, |_| {});
22462
22463    let fs = FakeFs::new(cx.executor());
22464    fs.insert_file(path!("/file.html"), Default::default())
22465        .await;
22466
22467    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22468
22469    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22470    let html_language = Arc::new(Language::new(
22471        LanguageConfig {
22472            name: "HTML".into(),
22473            matcher: LanguageMatcher {
22474                path_suffixes: vec!["html".to_string()],
22475                ..LanguageMatcher::default()
22476            },
22477            brackets: BracketPairConfig {
22478                pairs: vec![BracketPair {
22479                    start: "<".into(),
22480                    end: ">".into(),
22481                    close: true,
22482                    ..Default::default()
22483                }],
22484                ..Default::default()
22485            },
22486            ..Default::default()
22487        },
22488        Some(tree_sitter_html::LANGUAGE.into()),
22489    ));
22490    language_registry.add(html_language);
22491    let mut fake_servers = language_registry.register_fake_lsp(
22492        "HTML",
22493        FakeLspAdapter {
22494            capabilities: lsp::ServerCapabilities {
22495                completion_provider: Some(lsp::CompletionOptions {
22496                    resolve_provider: Some(true),
22497                    ..Default::default()
22498                }),
22499                ..Default::default()
22500            },
22501            ..Default::default()
22502        },
22503    );
22504
22505    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22506    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22507
22508    let worktree_id = workspace
22509        .update(cx, |workspace, _window, cx| {
22510            workspace.project().update(cx, |project, cx| {
22511                project.worktrees(cx).next().unwrap().read(cx).id()
22512            })
22513        })
22514        .unwrap();
22515    project
22516        .update(cx, |project, cx| {
22517            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22518        })
22519        .await
22520        .unwrap();
22521    let editor = workspace
22522        .update(cx, |workspace, window, cx| {
22523            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22524        })
22525        .unwrap()
22526        .await
22527        .unwrap()
22528        .downcast::<Editor>()
22529        .unwrap();
22530
22531    let fake_server = fake_servers.next().await.unwrap();
22532    editor.update_in(cx, |editor, window, cx| {
22533        editor.set_text("<ad></ad>", window, cx);
22534        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22535            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22536        });
22537        let Some((buffer, _)) = editor
22538            .buffer
22539            .read(cx)
22540            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22541        else {
22542            panic!("Failed to get buffer for selection position");
22543        };
22544        let buffer = buffer.read(cx);
22545        let buffer_id = buffer.remote_id();
22546        let opening_range =
22547            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22548        let closing_range =
22549            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22550        let mut linked_ranges = HashMap::default();
22551        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
22552        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22553    });
22554    let mut completion_handle =
22555        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22556            Ok(Some(lsp::CompletionResponse::Array(vec![
22557                lsp::CompletionItem {
22558                    label: "head".to_string(),
22559                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22560                        lsp::InsertReplaceEdit {
22561                            new_text: "head".to_string(),
22562                            insert: lsp::Range::new(
22563                                lsp::Position::new(0, 1),
22564                                lsp::Position::new(0, 3),
22565                            ),
22566                            replace: lsp::Range::new(
22567                                lsp::Position::new(0, 1),
22568                                lsp::Position::new(0, 3),
22569                            ),
22570                        },
22571                    )),
22572                    ..Default::default()
22573                },
22574            ])))
22575        });
22576    editor.update_in(cx, |editor, window, cx| {
22577        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22578    });
22579    cx.run_until_parked();
22580    completion_handle.next().await.unwrap();
22581    editor.update(cx, |editor, _| {
22582        assert!(
22583            editor.context_menu_visible(),
22584            "Completion menu should be visible"
22585        );
22586    });
22587    editor.update_in(cx, |editor, window, cx| {
22588        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22589    });
22590    cx.executor().run_until_parked();
22591    editor.update(cx, |editor, cx| {
22592        assert_eq!(editor.text(cx), "<head></head>");
22593    });
22594}
22595
22596#[gpui::test]
22597async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22598    init_test(cx, |_| {});
22599
22600    let fs = FakeFs::new(cx.executor());
22601    fs.insert_tree(
22602        path!("/root"),
22603        json!({
22604            "a": {
22605                "main.rs": "fn main() {}",
22606            },
22607            "foo": {
22608                "bar": {
22609                    "external_file.rs": "pub mod external {}",
22610                }
22611            }
22612        }),
22613    )
22614    .await;
22615
22616    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22617    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22618    language_registry.add(rust_lang());
22619    let _fake_servers = language_registry.register_fake_lsp(
22620        "Rust",
22621        FakeLspAdapter {
22622            ..FakeLspAdapter::default()
22623        },
22624    );
22625    let (workspace, cx) =
22626        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22627    let worktree_id = workspace.update(cx, |workspace, cx| {
22628        workspace.project().update(cx, |project, cx| {
22629            project.worktrees(cx).next().unwrap().read(cx).id()
22630        })
22631    });
22632
22633    let assert_language_servers_count =
22634        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22635            project.update(cx, |project, cx| {
22636                let current = project
22637                    .lsp_store()
22638                    .read(cx)
22639                    .as_local()
22640                    .unwrap()
22641                    .language_servers
22642                    .len();
22643                assert_eq!(expected, current, "{context}");
22644            });
22645        };
22646
22647    assert_language_servers_count(
22648        0,
22649        "No servers should be running before any file is open",
22650        cx,
22651    );
22652    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22653    let main_editor = workspace
22654        .update_in(cx, |workspace, window, cx| {
22655            workspace.open_path(
22656                (worktree_id, "main.rs"),
22657                Some(pane.downgrade()),
22658                true,
22659                window,
22660                cx,
22661            )
22662        })
22663        .unwrap()
22664        .await
22665        .downcast::<Editor>()
22666        .unwrap();
22667    pane.update(cx, |pane, cx| {
22668        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22669        open_editor.update(cx, |editor, cx| {
22670            assert_eq!(
22671                editor.display_text(cx),
22672                "fn main() {}",
22673                "Original main.rs text on initial open",
22674            );
22675        });
22676        assert_eq!(open_editor, main_editor);
22677    });
22678    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22679
22680    let external_editor = workspace
22681        .update_in(cx, |workspace, window, cx| {
22682            workspace.open_abs_path(
22683                PathBuf::from("/root/foo/bar/external_file.rs"),
22684                OpenOptions::default(),
22685                window,
22686                cx,
22687            )
22688        })
22689        .await
22690        .expect("opening external file")
22691        .downcast::<Editor>()
22692        .expect("downcasted external file's open element to editor");
22693    pane.update(cx, |pane, cx| {
22694        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22695        open_editor.update(cx, |editor, cx| {
22696            assert_eq!(
22697                editor.display_text(cx),
22698                "pub mod external {}",
22699                "External file is open now",
22700            );
22701        });
22702        assert_eq!(open_editor, external_editor);
22703    });
22704    assert_language_servers_count(
22705        1,
22706        "Second, external, *.rs file should join the existing server",
22707        cx,
22708    );
22709
22710    pane.update_in(cx, |pane, window, cx| {
22711        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22712    })
22713    .await
22714    .unwrap();
22715    pane.update_in(cx, |pane, window, cx| {
22716        pane.navigate_backward(window, cx);
22717    });
22718    cx.run_until_parked();
22719    pane.update(cx, |pane, cx| {
22720        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22721        open_editor.update(cx, |editor, cx| {
22722            assert_eq!(
22723                editor.display_text(cx),
22724                "pub mod external {}",
22725                "External file is open now",
22726            );
22727        });
22728    });
22729    assert_language_servers_count(
22730        1,
22731        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22732        cx,
22733    );
22734
22735    cx.update(|_, cx| {
22736        workspace::reload(cx);
22737    });
22738    assert_language_servers_count(
22739        1,
22740        "After reloading the worktree with local and external files opened, only one project should be started",
22741        cx,
22742    );
22743}
22744
22745#[gpui::test]
22746async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22747    init_test(cx, |_| {});
22748
22749    let mut cx = EditorTestContext::new(cx).await;
22750    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22751    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22752
22753    // test cursor move to start of each line on tab
22754    // for `if`, `elif`, `else`, `while`, `with` and `for`
22755    cx.set_state(indoc! {"
22756        def main():
22757        ˇ    for item in items:
22758        ˇ        while item.active:
22759        ˇ            if item.value > 10:
22760        ˇ                continue
22761        ˇ            elif item.value < 0:
22762        ˇ                break
22763        ˇ            else:
22764        ˇ                with item.context() as ctx:
22765        ˇ                    yield count
22766        ˇ        else:
22767        ˇ            log('while else')
22768        ˇ    else:
22769        ˇ        log('for else')
22770    "});
22771    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22772    cx.assert_editor_state(indoc! {"
22773        def main():
22774            ˇfor item in items:
22775                ˇwhile item.active:
22776                    ˇif item.value > 10:
22777                        ˇcontinue
22778                    ˇelif item.value < 0:
22779                        ˇbreak
22780                    ˇelse:
22781                        ˇwith item.context() as ctx:
22782                            ˇyield count
22783                ˇelse:
22784                    ˇlog('while else')
22785            ˇelse:
22786                ˇlog('for else')
22787    "});
22788    // test relative indent is preserved when tab
22789    // for `if`, `elif`, `else`, `while`, `with` and `for`
22790    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22791    cx.assert_editor_state(indoc! {"
22792        def main():
22793                ˇfor item in items:
22794                    ˇwhile item.active:
22795                        ˇif item.value > 10:
22796                            ˇcontinue
22797                        ˇelif item.value < 0:
22798                            ˇbreak
22799                        ˇelse:
22800                            ˇwith item.context() as ctx:
22801                                ˇyield count
22802                    ˇelse:
22803                        ˇlog('while else')
22804                ˇelse:
22805                    ˇlog('for else')
22806    "});
22807
22808    // test cursor move to start of each line on tab
22809    // for `try`, `except`, `else`, `finally`, `match` and `def`
22810    cx.set_state(indoc! {"
22811        def main():
22812        ˇ    try:
22813        ˇ        fetch()
22814        ˇ    except ValueError:
22815        ˇ        handle_error()
22816        ˇ    else:
22817        ˇ        match value:
22818        ˇ            case _:
22819        ˇ    finally:
22820        ˇ        def status():
22821        ˇ            return 0
22822    "});
22823    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22824    cx.assert_editor_state(indoc! {"
22825        def main():
22826            ˇtry:
22827                ˇfetch()
22828            ˇexcept ValueError:
22829                ˇhandle_error()
22830            ˇelse:
22831                ˇmatch value:
22832                    ˇcase _:
22833            ˇfinally:
22834                ˇdef status():
22835                    ˇreturn 0
22836    "});
22837    // test relative indent is preserved when tab
22838    // for `try`, `except`, `else`, `finally`, `match` and `def`
22839    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22840    cx.assert_editor_state(indoc! {"
22841        def main():
22842                ˇtry:
22843                    ˇfetch()
22844                ˇexcept ValueError:
22845                    ˇhandle_error()
22846                ˇelse:
22847                    ˇmatch value:
22848                        ˇcase _:
22849                ˇfinally:
22850                    ˇdef status():
22851                        ˇreturn 0
22852    "});
22853}
22854
22855#[gpui::test]
22856async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22857    init_test(cx, |_| {});
22858
22859    let mut cx = EditorTestContext::new(cx).await;
22860    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22861    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22862
22863    // test `else` auto outdents when typed inside `if` block
22864    cx.set_state(indoc! {"
22865        def main():
22866            if i == 2:
22867                return
22868                ˇ
22869    "});
22870    cx.update_editor(|editor, window, cx| {
22871        editor.handle_input("else:", window, cx);
22872    });
22873    cx.assert_editor_state(indoc! {"
22874        def main():
22875            if i == 2:
22876                return
22877            else:ˇ
22878    "});
22879
22880    // test `except` auto outdents when typed inside `try` block
22881    cx.set_state(indoc! {"
22882        def main():
22883            try:
22884                i = 2
22885                ˇ
22886    "});
22887    cx.update_editor(|editor, window, cx| {
22888        editor.handle_input("except:", window, cx);
22889    });
22890    cx.assert_editor_state(indoc! {"
22891        def main():
22892            try:
22893                i = 2
22894            except:ˇ
22895    "});
22896
22897    // test `else` auto outdents when typed inside `except` block
22898    cx.set_state(indoc! {"
22899        def main():
22900            try:
22901                i = 2
22902            except:
22903                j = 2
22904                ˇ
22905    "});
22906    cx.update_editor(|editor, window, cx| {
22907        editor.handle_input("else:", window, cx);
22908    });
22909    cx.assert_editor_state(indoc! {"
22910        def main():
22911            try:
22912                i = 2
22913            except:
22914                j = 2
22915            else:ˇ
22916    "});
22917
22918    // test `finally` auto outdents when typed inside `else` block
22919    cx.set_state(indoc! {"
22920        def main():
22921            try:
22922                i = 2
22923            except:
22924                j = 2
22925            else:
22926                k = 2
22927                ˇ
22928    "});
22929    cx.update_editor(|editor, window, cx| {
22930        editor.handle_input("finally:", window, cx);
22931    });
22932    cx.assert_editor_state(indoc! {"
22933        def main():
22934            try:
22935                i = 2
22936            except:
22937                j = 2
22938            else:
22939                k = 2
22940            finally:ˇ
22941    "});
22942
22943    // test `else` does not outdents when typed inside `except` block right after for block
22944    cx.set_state(indoc! {"
22945        def main():
22946            try:
22947                i = 2
22948            except:
22949                for i in range(n):
22950                    pass
22951                ˇ
22952    "});
22953    cx.update_editor(|editor, window, cx| {
22954        editor.handle_input("else:", window, cx);
22955    });
22956    cx.assert_editor_state(indoc! {"
22957        def main():
22958            try:
22959                i = 2
22960            except:
22961                for i in range(n):
22962                    pass
22963                else:ˇ
22964    "});
22965
22966    // test `finally` auto outdents when typed inside `else` block right after for block
22967    cx.set_state(indoc! {"
22968        def main():
22969            try:
22970                i = 2
22971            except:
22972                j = 2
22973            else:
22974                for i in range(n):
22975                    pass
22976                ˇ
22977    "});
22978    cx.update_editor(|editor, window, cx| {
22979        editor.handle_input("finally:", window, cx);
22980    });
22981    cx.assert_editor_state(indoc! {"
22982        def main():
22983            try:
22984                i = 2
22985            except:
22986                j = 2
22987            else:
22988                for i in range(n):
22989                    pass
22990            finally:ˇ
22991    "});
22992
22993    // test `except` outdents to inner "try" block
22994    cx.set_state(indoc! {"
22995        def main():
22996            try:
22997                i = 2
22998                if i == 2:
22999                    try:
23000                        i = 3
23001                        ˇ
23002    "});
23003    cx.update_editor(|editor, window, cx| {
23004        editor.handle_input("except:", window, cx);
23005    });
23006    cx.assert_editor_state(indoc! {"
23007        def main():
23008            try:
23009                i = 2
23010                if i == 2:
23011                    try:
23012                        i = 3
23013                    except:ˇ
23014    "});
23015
23016    // test `except` outdents to outer "try" block
23017    cx.set_state(indoc! {"
23018        def main():
23019            try:
23020                i = 2
23021                if i == 2:
23022                    try:
23023                        i = 3
23024                ˇ
23025    "});
23026    cx.update_editor(|editor, window, cx| {
23027        editor.handle_input("except:", window, cx);
23028    });
23029    cx.assert_editor_state(indoc! {"
23030        def main():
23031            try:
23032                i = 2
23033                if i == 2:
23034                    try:
23035                        i = 3
23036            except:ˇ
23037    "});
23038
23039    // test `else` stays at correct indent when typed after `for` block
23040    cx.set_state(indoc! {"
23041        def main():
23042            for i in range(10):
23043                if i == 3:
23044                    break
23045            ˇ
23046    "});
23047    cx.update_editor(|editor, window, cx| {
23048        editor.handle_input("else:", window, cx);
23049    });
23050    cx.assert_editor_state(indoc! {"
23051        def main():
23052            for i in range(10):
23053                if i == 3:
23054                    break
23055            else:ˇ
23056    "});
23057
23058    // test does not outdent on typing after line with square brackets
23059    cx.set_state(indoc! {"
23060        def f() -> list[str]:
23061            ˇ
23062    "});
23063    cx.update_editor(|editor, window, cx| {
23064        editor.handle_input("a", window, cx);
23065    });
23066    cx.assert_editor_state(indoc! {"
23067        def f() -> list[str]:
2306823069    "});
23070
23071    // test does not outdent on typing : after case keyword
23072    cx.set_state(indoc! {"
23073        match 1:
23074            caseˇ
23075    "});
23076    cx.update_editor(|editor, window, cx| {
23077        editor.handle_input(":", window, cx);
23078    });
23079    cx.assert_editor_state(indoc! {"
23080        match 1:
23081            case:ˇ
23082    "});
23083}
23084
23085#[gpui::test]
23086async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23087    init_test(cx, |_| {});
23088    update_test_language_settings(cx, |settings| {
23089        settings.defaults.extend_comment_on_newline = Some(false);
23090    });
23091    let mut cx = EditorTestContext::new(cx).await;
23092    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23093    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23094
23095    // test correct indent after newline on comment
23096    cx.set_state(indoc! {"
23097        # COMMENT:ˇ
23098    "});
23099    cx.update_editor(|editor, window, cx| {
23100        editor.newline(&Newline, window, cx);
23101    });
23102    cx.assert_editor_state(indoc! {"
23103        # COMMENT:
23104        ˇ
23105    "});
23106
23107    // test correct indent after newline in brackets
23108    cx.set_state(indoc! {"
23109        {ˇ}
23110    "});
23111    cx.update_editor(|editor, window, cx| {
23112        editor.newline(&Newline, window, cx);
23113    });
23114    cx.run_until_parked();
23115    cx.assert_editor_state(indoc! {"
23116        {
23117            ˇ
23118        }
23119    "});
23120
23121    cx.set_state(indoc! {"
23122        (ˇ)
23123    "});
23124    cx.update_editor(|editor, window, cx| {
23125        editor.newline(&Newline, window, cx);
23126    });
23127    cx.run_until_parked();
23128    cx.assert_editor_state(indoc! {"
23129        (
23130            ˇ
23131        )
23132    "});
23133
23134    // do not indent after empty lists or dictionaries
23135    cx.set_state(indoc! {"
23136        a = []ˇ
23137    "});
23138    cx.update_editor(|editor, window, cx| {
23139        editor.newline(&Newline, window, cx);
23140    });
23141    cx.run_until_parked();
23142    cx.assert_editor_state(indoc! {"
23143        a = []
23144        ˇ
23145    "});
23146}
23147
23148#[gpui::test]
23149async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23150    init_test(cx, |_| {});
23151
23152    let mut cx = EditorTestContext::new(cx).await;
23153    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23154    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23155
23156    // test cursor move to start of each line on tab
23157    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23158    cx.set_state(indoc! {"
23159        function main() {
23160        ˇ    for item in $items; do
23161        ˇ        while [ -n \"$item\" ]; do
23162        ˇ            if [ \"$value\" -gt 10 ]; then
23163        ˇ                continue
23164        ˇ            elif [ \"$value\" -lt 0 ]; then
23165        ˇ                break
23166        ˇ            else
23167        ˇ                echo \"$item\"
23168        ˇ            fi
23169        ˇ        done
23170        ˇ    done
23171        ˇ}
23172    "});
23173    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23174    cx.assert_editor_state(indoc! {"
23175        function main() {
23176            ˇfor item in $items; do
23177                ˇwhile [ -n \"$item\" ]; do
23178                    ˇif [ \"$value\" -gt 10 ]; then
23179                        ˇcontinue
23180                    ˇelif [ \"$value\" -lt 0 ]; then
23181                        ˇbreak
23182                    ˇelse
23183                        ˇecho \"$item\"
23184                    ˇfi
23185                ˇdone
23186            ˇdone
23187        ˇ}
23188    "});
23189    // test relative indent is preserved when tab
23190    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23191    cx.assert_editor_state(indoc! {"
23192        function main() {
23193                ˇfor item in $items; do
23194                    ˇwhile [ -n \"$item\" ]; do
23195                        ˇif [ \"$value\" -gt 10 ]; then
23196                            ˇcontinue
23197                        ˇelif [ \"$value\" -lt 0 ]; then
23198                            ˇbreak
23199                        ˇelse
23200                            ˇecho \"$item\"
23201                        ˇfi
23202                    ˇdone
23203                ˇdone
23204            ˇ}
23205    "});
23206
23207    // test cursor move to start of each line on tab
23208    // for `case` statement with patterns
23209    cx.set_state(indoc! {"
23210        function handle() {
23211        ˇ    case \"$1\" in
23212        ˇ        start)
23213        ˇ            echo \"a\"
23214        ˇ            ;;
23215        ˇ        stop)
23216        ˇ            echo \"b\"
23217        ˇ            ;;
23218        ˇ        *)
23219        ˇ            echo \"c\"
23220        ˇ            ;;
23221        ˇ    esac
23222        ˇ}
23223    "});
23224    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23225    cx.assert_editor_state(indoc! {"
23226        function handle() {
23227            ˇcase \"$1\" in
23228                ˇstart)
23229                    ˇecho \"a\"
23230                    ˇ;;
23231                ˇstop)
23232                    ˇecho \"b\"
23233                    ˇ;;
23234                ˇ*)
23235                    ˇecho \"c\"
23236                    ˇ;;
23237            ˇesac
23238        ˇ}
23239    "});
23240}
23241
23242#[gpui::test]
23243async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23244    init_test(cx, |_| {});
23245
23246    let mut cx = EditorTestContext::new(cx).await;
23247    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23248    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23249
23250    // test indents on comment insert
23251    cx.set_state(indoc! {"
23252        function main() {
23253        ˇ    for item in $items; do
23254        ˇ        while [ -n \"$item\" ]; do
23255        ˇ            if [ \"$value\" -gt 10 ]; then
23256        ˇ                continue
23257        ˇ            elif [ \"$value\" -lt 0 ]; then
23258        ˇ                break
23259        ˇ            else
23260        ˇ                echo \"$item\"
23261        ˇ            fi
23262        ˇ        done
23263        ˇ    done
23264        ˇ}
23265    "});
23266    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23267    cx.assert_editor_state(indoc! {"
23268        function main() {
23269        #ˇ    for item in $items; do
23270        #ˇ        while [ -n \"$item\" ]; do
23271        #ˇ            if [ \"$value\" -gt 10 ]; then
23272        #ˇ                continue
23273        #ˇ            elif [ \"$value\" -lt 0 ]; then
23274        #ˇ                break
23275        #ˇ            else
23276        #ˇ                echo \"$item\"
23277        #ˇ            fi
23278        #ˇ        done
23279        #ˇ    done
23280        #ˇ}
23281    "});
23282}
23283
23284#[gpui::test]
23285async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23286    init_test(cx, |_| {});
23287
23288    let mut cx = EditorTestContext::new(cx).await;
23289    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23290    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23291
23292    // test `else` auto outdents when typed inside `if` block
23293    cx.set_state(indoc! {"
23294        if [ \"$1\" = \"test\" ]; then
23295            echo \"foo bar\"
23296            ˇ
23297    "});
23298    cx.update_editor(|editor, window, cx| {
23299        editor.handle_input("else", window, cx);
23300    });
23301    cx.assert_editor_state(indoc! {"
23302        if [ \"$1\" = \"test\" ]; then
23303            echo \"foo bar\"
23304        elseˇ
23305    "});
23306
23307    // test `elif` auto outdents when typed inside `if` block
23308    cx.set_state(indoc! {"
23309        if [ \"$1\" = \"test\" ]; then
23310            echo \"foo bar\"
23311            ˇ
23312    "});
23313    cx.update_editor(|editor, window, cx| {
23314        editor.handle_input("elif", window, cx);
23315    });
23316    cx.assert_editor_state(indoc! {"
23317        if [ \"$1\" = \"test\" ]; then
23318            echo \"foo bar\"
23319        elifˇ
23320    "});
23321
23322    // test `fi` auto outdents when typed inside `else` block
23323    cx.set_state(indoc! {"
23324        if [ \"$1\" = \"test\" ]; then
23325            echo \"foo bar\"
23326        else
23327            echo \"bar baz\"
23328            ˇ
23329    "});
23330    cx.update_editor(|editor, window, cx| {
23331        editor.handle_input("fi", window, cx);
23332    });
23333    cx.assert_editor_state(indoc! {"
23334        if [ \"$1\" = \"test\" ]; then
23335            echo \"foo bar\"
23336        else
23337            echo \"bar baz\"
23338        fiˇ
23339    "});
23340
23341    // test `done` auto outdents when typed inside `while` block
23342    cx.set_state(indoc! {"
23343        while read line; do
23344            echo \"$line\"
23345            ˇ
23346    "});
23347    cx.update_editor(|editor, window, cx| {
23348        editor.handle_input("done", window, cx);
23349    });
23350    cx.assert_editor_state(indoc! {"
23351        while read line; do
23352            echo \"$line\"
23353        doneˇ
23354    "});
23355
23356    // test `done` auto outdents when typed inside `for` block
23357    cx.set_state(indoc! {"
23358        for file in *.txt; do
23359            cat \"$file\"
23360            ˇ
23361    "});
23362    cx.update_editor(|editor, window, cx| {
23363        editor.handle_input("done", window, cx);
23364    });
23365    cx.assert_editor_state(indoc! {"
23366        for file in *.txt; do
23367            cat \"$file\"
23368        doneˇ
23369    "});
23370
23371    // test `esac` auto outdents when typed inside `case` block
23372    cx.set_state(indoc! {"
23373        case \"$1\" in
23374            start)
23375                echo \"foo bar\"
23376                ;;
23377            stop)
23378                echo \"bar baz\"
23379                ;;
23380            ˇ
23381    "});
23382    cx.update_editor(|editor, window, cx| {
23383        editor.handle_input("esac", window, cx);
23384    });
23385    cx.assert_editor_state(indoc! {"
23386        case \"$1\" in
23387            start)
23388                echo \"foo bar\"
23389                ;;
23390            stop)
23391                echo \"bar baz\"
23392                ;;
23393        esacˇ
23394    "});
23395
23396    // test `*)` auto outdents when typed inside `case` block
23397    cx.set_state(indoc! {"
23398        case \"$1\" in
23399            start)
23400                echo \"foo bar\"
23401                ;;
23402                ˇ
23403    "});
23404    cx.update_editor(|editor, window, cx| {
23405        editor.handle_input("*)", window, cx);
23406    });
23407    cx.assert_editor_state(indoc! {"
23408        case \"$1\" in
23409            start)
23410                echo \"foo bar\"
23411                ;;
23412            *)ˇ
23413    "});
23414
23415    // test `fi` outdents to correct level with nested if blocks
23416    cx.set_state(indoc! {"
23417        if [ \"$1\" = \"test\" ]; then
23418            echo \"outer if\"
23419            if [ \"$2\" = \"debug\" ]; then
23420                echo \"inner if\"
23421                ˇ
23422    "});
23423    cx.update_editor(|editor, window, cx| {
23424        editor.handle_input("fi", window, cx);
23425    });
23426    cx.assert_editor_state(indoc! {"
23427        if [ \"$1\" = \"test\" ]; then
23428            echo \"outer if\"
23429            if [ \"$2\" = \"debug\" ]; then
23430                echo \"inner if\"
23431            fiˇ
23432    "});
23433}
23434
23435#[gpui::test]
23436async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23437    init_test(cx, |_| {});
23438    update_test_language_settings(cx, |settings| {
23439        settings.defaults.extend_comment_on_newline = Some(false);
23440    });
23441    let mut cx = EditorTestContext::new(cx).await;
23442    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23443    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23444
23445    // test correct indent after newline on comment
23446    cx.set_state(indoc! {"
23447        # COMMENT:ˇ
23448    "});
23449    cx.update_editor(|editor, window, cx| {
23450        editor.newline(&Newline, window, cx);
23451    });
23452    cx.assert_editor_state(indoc! {"
23453        # COMMENT:
23454        ˇ
23455    "});
23456
23457    // test correct indent after newline after `then`
23458    cx.set_state(indoc! {"
23459
23460        if [ \"$1\" = \"test\" ]; thenˇ
23461    "});
23462    cx.update_editor(|editor, window, cx| {
23463        editor.newline(&Newline, window, cx);
23464    });
23465    cx.run_until_parked();
23466    cx.assert_editor_state(indoc! {"
23467
23468        if [ \"$1\" = \"test\" ]; then
23469            ˇ
23470    "});
23471
23472    // test correct indent after newline after `else`
23473    cx.set_state(indoc! {"
23474        if [ \"$1\" = \"test\" ]; then
23475        elseˇ
23476    "});
23477    cx.update_editor(|editor, window, cx| {
23478        editor.newline(&Newline, window, cx);
23479    });
23480    cx.run_until_parked();
23481    cx.assert_editor_state(indoc! {"
23482        if [ \"$1\" = \"test\" ]; then
23483        else
23484            ˇ
23485    "});
23486
23487    // test correct indent after newline after `elif`
23488    cx.set_state(indoc! {"
23489        if [ \"$1\" = \"test\" ]; then
23490        elifˇ
23491    "});
23492    cx.update_editor(|editor, window, cx| {
23493        editor.newline(&Newline, window, cx);
23494    });
23495    cx.run_until_parked();
23496    cx.assert_editor_state(indoc! {"
23497        if [ \"$1\" = \"test\" ]; then
23498        elif
23499            ˇ
23500    "});
23501
23502    // test correct indent after newline after `do`
23503    cx.set_state(indoc! {"
23504        for file in *.txt; doˇ
23505    "});
23506    cx.update_editor(|editor, window, cx| {
23507        editor.newline(&Newline, window, cx);
23508    });
23509    cx.run_until_parked();
23510    cx.assert_editor_state(indoc! {"
23511        for file in *.txt; do
23512            ˇ
23513    "});
23514
23515    // test correct indent after newline after case pattern
23516    cx.set_state(indoc! {"
23517        case \"$1\" in
23518            start)ˇ
23519    "});
23520    cx.update_editor(|editor, window, cx| {
23521        editor.newline(&Newline, window, cx);
23522    });
23523    cx.run_until_parked();
23524    cx.assert_editor_state(indoc! {"
23525        case \"$1\" in
23526            start)
23527                ˇ
23528    "});
23529
23530    // test correct indent after newline after case pattern
23531    cx.set_state(indoc! {"
23532        case \"$1\" in
23533            start)
23534                ;;
23535            *)ˇ
23536    "});
23537    cx.update_editor(|editor, window, cx| {
23538        editor.newline(&Newline, window, cx);
23539    });
23540    cx.run_until_parked();
23541    cx.assert_editor_state(indoc! {"
23542        case \"$1\" in
23543            start)
23544                ;;
23545            *)
23546                ˇ
23547    "});
23548
23549    // test correct indent after newline after function opening brace
23550    cx.set_state(indoc! {"
23551        function test() {ˇ}
23552    "});
23553    cx.update_editor(|editor, window, cx| {
23554        editor.newline(&Newline, window, cx);
23555    });
23556    cx.run_until_parked();
23557    cx.assert_editor_state(indoc! {"
23558        function test() {
23559            ˇ
23560        }
23561    "});
23562
23563    // test no extra indent after semicolon on same line
23564    cx.set_state(indoc! {"
23565        echo \"test\"23566    "});
23567    cx.update_editor(|editor, window, cx| {
23568        editor.newline(&Newline, window, cx);
23569    });
23570    cx.run_until_parked();
23571    cx.assert_editor_state(indoc! {"
23572        echo \"test\";
23573        ˇ
23574    "});
23575}
23576
23577fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23578    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23579    point..point
23580}
23581
23582#[track_caller]
23583fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23584    let (text, ranges) = marked_text_ranges(marked_text, true);
23585    assert_eq!(editor.text(cx), text);
23586    assert_eq!(
23587        editor.selections.ranges(cx),
23588        ranges,
23589        "Assert selections are {}",
23590        marked_text
23591    );
23592}
23593
23594pub fn handle_signature_help_request(
23595    cx: &mut EditorLspTestContext,
23596    mocked_response: lsp::SignatureHelp,
23597) -> impl Future<Output = ()> + use<> {
23598    let mut request =
23599        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23600            let mocked_response = mocked_response.clone();
23601            async move { Ok(Some(mocked_response)) }
23602        });
23603
23604    async move {
23605        request.next().await;
23606    }
23607}
23608
23609#[track_caller]
23610pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23611    cx.update_editor(|editor, _, _| {
23612        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23613            let entries = menu.entries.borrow();
23614            let entries = entries
23615                .iter()
23616                .map(|entry| entry.string.as_str())
23617                .collect::<Vec<_>>();
23618            assert_eq!(entries, expected);
23619        } else {
23620            panic!("Expected completions menu");
23621        }
23622    });
23623}
23624
23625/// Handle completion request passing a marked string specifying where the completion
23626/// should be triggered from using '|' character, what range should be replaced, and what completions
23627/// should be returned using '<' and '>' to delimit the range.
23628///
23629/// Also see `handle_completion_request_with_insert_and_replace`.
23630#[track_caller]
23631pub fn handle_completion_request(
23632    marked_string: &str,
23633    completions: Vec<&'static str>,
23634    is_incomplete: bool,
23635    counter: Arc<AtomicUsize>,
23636    cx: &mut EditorLspTestContext,
23637) -> impl Future<Output = ()> {
23638    let complete_from_marker: TextRangeMarker = '|'.into();
23639    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23640    let (_, mut marked_ranges) = marked_text_ranges_by(
23641        marked_string,
23642        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23643    );
23644
23645    let complete_from_position =
23646        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23647    let replace_range =
23648        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23649
23650    let mut request =
23651        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23652            let completions = completions.clone();
23653            counter.fetch_add(1, atomic::Ordering::Release);
23654            async move {
23655                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23656                assert_eq!(
23657                    params.text_document_position.position,
23658                    complete_from_position
23659                );
23660                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23661                    is_incomplete,
23662                    item_defaults: None,
23663                    items: completions
23664                        .iter()
23665                        .map(|completion_text| lsp::CompletionItem {
23666                            label: completion_text.to_string(),
23667                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23668                                range: replace_range,
23669                                new_text: completion_text.to_string(),
23670                            })),
23671                            ..Default::default()
23672                        })
23673                        .collect(),
23674                })))
23675            }
23676        });
23677
23678    async move {
23679        request.next().await;
23680    }
23681}
23682
23683/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23684/// given instead, which also contains an `insert` range.
23685///
23686/// This function uses markers to define ranges:
23687/// - `|` marks the cursor position
23688/// - `<>` marks the replace range
23689/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23690pub fn handle_completion_request_with_insert_and_replace(
23691    cx: &mut EditorLspTestContext,
23692    marked_string: &str,
23693    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23694    counter: Arc<AtomicUsize>,
23695) -> impl Future<Output = ()> {
23696    let complete_from_marker: TextRangeMarker = '|'.into();
23697    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23698    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23699
23700    let (_, mut marked_ranges) = marked_text_ranges_by(
23701        marked_string,
23702        vec![
23703            complete_from_marker.clone(),
23704            replace_range_marker.clone(),
23705            insert_range_marker.clone(),
23706        ],
23707    );
23708
23709    let complete_from_position =
23710        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23711    let replace_range =
23712        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23713
23714    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23715        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23716        _ => lsp::Range {
23717            start: replace_range.start,
23718            end: complete_from_position,
23719        },
23720    };
23721
23722    let mut request =
23723        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23724            let completions = completions.clone();
23725            counter.fetch_add(1, atomic::Ordering::Release);
23726            async move {
23727                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23728                assert_eq!(
23729                    params.text_document_position.position, complete_from_position,
23730                    "marker `|` position doesn't match",
23731                );
23732                Ok(Some(lsp::CompletionResponse::Array(
23733                    completions
23734                        .iter()
23735                        .map(|(label, new_text)| lsp::CompletionItem {
23736                            label: label.to_string(),
23737                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23738                                lsp::InsertReplaceEdit {
23739                                    insert: insert_range,
23740                                    replace: replace_range,
23741                                    new_text: new_text.to_string(),
23742                                },
23743                            )),
23744                            ..Default::default()
23745                        })
23746                        .collect(),
23747                )))
23748            }
23749        });
23750
23751    async move {
23752        request.next().await;
23753    }
23754}
23755
23756fn handle_resolve_completion_request(
23757    cx: &mut EditorLspTestContext,
23758    edits: Option<Vec<(&'static str, &'static str)>>,
23759) -> impl Future<Output = ()> {
23760    let edits = edits.map(|edits| {
23761        edits
23762            .iter()
23763            .map(|(marked_string, new_text)| {
23764                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23765                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23766                lsp::TextEdit::new(replace_range, new_text.to_string())
23767            })
23768            .collect::<Vec<_>>()
23769    });
23770
23771    let mut request =
23772        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23773            let edits = edits.clone();
23774            async move {
23775                Ok(lsp::CompletionItem {
23776                    additional_text_edits: edits,
23777                    ..Default::default()
23778                })
23779            }
23780        });
23781
23782    async move {
23783        request.next().await;
23784    }
23785}
23786
23787pub(crate) fn update_test_language_settings(
23788    cx: &mut TestAppContext,
23789    f: impl Fn(&mut AllLanguageSettingsContent),
23790) {
23791    cx.update(|cx| {
23792        SettingsStore::update_global(cx, |store, cx| {
23793            store.update_user_settings::<AllLanguageSettings>(cx, f);
23794        });
23795    });
23796}
23797
23798pub(crate) fn update_test_project_settings(
23799    cx: &mut TestAppContext,
23800    f: impl Fn(&mut ProjectSettings),
23801) {
23802    cx.update(|cx| {
23803        SettingsStore::update_global(cx, |store, cx| {
23804            store.update_user_settings::<ProjectSettings>(cx, f);
23805        });
23806    });
23807}
23808
23809pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23810    cx.update(|cx| {
23811        assets::Assets.load_test_fonts(cx);
23812        let store = SettingsStore::test(cx);
23813        cx.set_global(store);
23814        theme::init(theme::LoadThemes::JustBase, cx);
23815        release_channel::init(SemanticVersion::default(), cx);
23816        client::init_settings(cx);
23817        language::init(cx);
23818        Project::init_settings(cx);
23819        workspace::init_settings(cx);
23820        crate::init(cx);
23821    });
23822    zlog::init_test();
23823    update_test_language_settings(cx, f);
23824}
23825
23826#[track_caller]
23827fn assert_hunk_revert(
23828    not_reverted_text_with_selections: &str,
23829    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23830    expected_reverted_text_with_selections: &str,
23831    base_text: &str,
23832    cx: &mut EditorLspTestContext,
23833) {
23834    cx.set_state(not_reverted_text_with_selections);
23835    cx.set_head_text(base_text);
23836    cx.executor().run_until_parked();
23837
23838    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23839        let snapshot = editor.snapshot(window, cx);
23840        let reverted_hunk_statuses = snapshot
23841            .buffer_snapshot
23842            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23843            .map(|hunk| hunk.status().kind)
23844            .collect::<Vec<_>>();
23845
23846        editor.git_restore(&Default::default(), window, cx);
23847        reverted_hunk_statuses
23848    });
23849    cx.executor().run_until_parked();
23850    cx.assert_editor_state(expected_reverted_text_with_selections);
23851    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23852}
23853
23854#[gpui::test(iterations = 10)]
23855async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23856    init_test(cx, |_| {});
23857
23858    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23859    let counter = diagnostic_requests.clone();
23860
23861    let fs = FakeFs::new(cx.executor());
23862    fs.insert_tree(
23863        path!("/a"),
23864        json!({
23865            "first.rs": "fn main() { let a = 5; }",
23866            "second.rs": "// Test file",
23867        }),
23868    )
23869    .await;
23870
23871    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23872    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23873    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23874
23875    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23876    language_registry.add(rust_lang());
23877    let mut fake_servers = language_registry.register_fake_lsp(
23878        "Rust",
23879        FakeLspAdapter {
23880            capabilities: lsp::ServerCapabilities {
23881                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
23882                    lsp::DiagnosticOptions {
23883                        identifier: None,
23884                        inter_file_dependencies: true,
23885                        workspace_diagnostics: true,
23886                        work_done_progress_options: Default::default(),
23887                    },
23888                )),
23889                ..Default::default()
23890            },
23891            ..Default::default()
23892        },
23893    );
23894
23895    let editor = workspace
23896        .update(cx, |workspace, window, cx| {
23897            workspace.open_abs_path(
23898                PathBuf::from(path!("/a/first.rs")),
23899                OpenOptions::default(),
23900                window,
23901                cx,
23902            )
23903        })
23904        .unwrap()
23905        .await
23906        .unwrap()
23907        .downcast::<Editor>()
23908        .unwrap();
23909    let fake_server = fake_servers.next().await.unwrap();
23910    let server_id = fake_server.server.server_id();
23911    let mut first_request = fake_server
23912        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
23913            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
23914            let result_id = Some(new_result_id.to_string());
23915            assert_eq!(
23916                params.text_document.uri,
23917                lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
23918            );
23919            async move {
23920                Ok(lsp::DocumentDiagnosticReportResult::Report(
23921                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
23922                        related_documents: None,
23923                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
23924                            items: Vec::new(),
23925                            result_id,
23926                        },
23927                    }),
23928                ))
23929            }
23930        });
23931
23932    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
23933        project.update(cx, |project, cx| {
23934            let buffer_id = editor
23935                .read(cx)
23936                .buffer()
23937                .read(cx)
23938                .as_singleton()
23939                .expect("created a singleton buffer")
23940                .read(cx)
23941                .remote_id();
23942            let buffer_result_id = project
23943                .lsp_store()
23944                .read(cx)
23945                .result_id(server_id, buffer_id, cx);
23946            assert_eq!(expected, buffer_result_id);
23947        });
23948    };
23949
23950    ensure_result_id(None, cx);
23951    cx.executor().advance_clock(Duration::from_millis(60));
23952    cx.executor().run_until_parked();
23953    assert_eq!(
23954        diagnostic_requests.load(atomic::Ordering::Acquire),
23955        1,
23956        "Opening file should trigger diagnostic request"
23957    );
23958    first_request
23959        .next()
23960        .await
23961        .expect("should have sent the first diagnostics pull request");
23962    ensure_result_id(Some("1".to_string()), cx);
23963
23964    // Editing should trigger diagnostics
23965    editor.update_in(cx, |editor, window, cx| {
23966        editor.handle_input("2", window, cx)
23967    });
23968    cx.executor().advance_clock(Duration::from_millis(60));
23969    cx.executor().run_until_parked();
23970    assert_eq!(
23971        diagnostic_requests.load(atomic::Ordering::Acquire),
23972        2,
23973        "Editing should trigger diagnostic request"
23974    );
23975    ensure_result_id(Some("2".to_string()), cx);
23976
23977    // Moving cursor should not trigger diagnostic request
23978    editor.update_in(cx, |editor, window, cx| {
23979        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23980            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
23981        });
23982    });
23983    cx.executor().advance_clock(Duration::from_millis(60));
23984    cx.executor().run_until_parked();
23985    assert_eq!(
23986        diagnostic_requests.load(atomic::Ordering::Acquire),
23987        2,
23988        "Cursor movement should not trigger diagnostic request"
23989    );
23990    ensure_result_id(Some("2".to_string()), cx);
23991    // Multiple rapid edits should be debounced
23992    for _ in 0..5 {
23993        editor.update_in(cx, |editor, window, cx| {
23994            editor.handle_input("x", window, cx)
23995        });
23996    }
23997    cx.executor().advance_clock(Duration::from_millis(60));
23998    cx.executor().run_until_parked();
23999
24000    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24001    assert!(
24002        final_requests <= 4,
24003        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24004    );
24005    ensure_result_id(Some(final_requests.to_string()), cx);
24006}
24007
24008#[gpui::test]
24009async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24010    // Regression test for issue #11671
24011    // Previously, adding a cursor after moving multiple cursors would reset
24012    // the cursor count instead of adding to the existing cursors.
24013    init_test(cx, |_| {});
24014    let mut cx = EditorTestContext::new(cx).await;
24015
24016    // Create a simple buffer with cursor at start
24017    cx.set_state(indoc! {"
24018        ˇaaaa
24019        bbbb
24020        cccc
24021        dddd
24022        eeee
24023        ffff
24024        gggg
24025        hhhh"});
24026
24027    // Add 2 cursors below (so we have 3 total)
24028    cx.update_editor(|editor, window, cx| {
24029        editor.add_selection_below(&Default::default(), window, cx);
24030        editor.add_selection_below(&Default::default(), window, cx);
24031    });
24032
24033    // Verify we have 3 cursors
24034    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24035    assert_eq!(
24036        initial_count, 3,
24037        "Should have 3 cursors after adding 2 below"
24038    );
24039
24040    // Move down one line
24041    cx.update_editor(|editor, window, cx| {
24042        editor.move_down(&MoveDown, window, cx);
24043    });
24044
24045    // Add another cursor below
24046    cx.update_editor(|editor, window, cx| {
24047        editor.add_selection_below(&Default::default(), window, cx);
24048    });
24049
24050    // Should now have 4 cursors (3 original + 1 new)
24051    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24052    assert_eq!(
24053        final_count, 4,
24054        "Should have 4 cursors after moving and adding another"
24055    );
24056}
24057
24058#[gpui::test(iterations = 10)]
24059async fn test_document_colors(cx: &mut TestAppContext) {
24060    let expected_color = Rgba {
24061        r: 0.33,
24062        g: 0.33,
24063        b: 0.33,
24064        a: 0.33,
24065    };
24066
24067    init_test(cx, |_| {});
24068
24069    let fs = FakeFs::new(cx.executor());
24070    fs.insert_tree(
24071        path!("/a"),
24072        json!({
24073            "first.rs": "fn main() { let a = 5; }",
24074        }),
24075    )
24076    .await;
24077
24078    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24079    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24080    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24081
24082    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24083    language_registry.add(rust_lang());
24084    let mut fake_servers = language_registry.register_fake_lsp(
24085        "Rust",
24086        FakeLspAdapter {
24087            capabilities: lsp::ServerCapabilities {
24088                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24089                ..lsp::ServerCapabilities::default()
24090            },
24091            name: "rust-analyzer",
24092            ..FakeLspAdapter::default()
24093        },
24094    );
24095    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24096        "Rust",
24097        FakeLspAdapter {
24098            capabilities: lsp::ServerCapabilities {
24099                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24100                ..lsp::ServerCapabilities::default()
24101            },
24102            name: "not-rust-analyzer",
24103            ..FakeLspAdapter::default()
24104        },
24105    );
24106
24107    let editor = workspace
24108        .update(cx, |workspace, window, cx| {
24109            workspace.open_abs_path(
24110                PathBuf::from(path!("/a/first.rs")),
24111                OpenOptions::default(),
24112                window,
24113                cx,
24114            )
24115        })
24116        .unwrap()
24117        .await
24118        .unwrap()
24119        .downcast::<Editor>()
24120        .unwrap();
24121    let fake_language_server = fake_servers.next().await.unwrap();
24122    let fake_language_server_without_capabilities =
24123        fake_servers_without_capabilities.next().await.unwrap();
24124    let requests_made = Arc::new(AtomicUsize::new(0));
24125    let closure_requests_made = Arc::clone(&requests_made);
24126    let mut color_request_handle = fake_language_server
24127        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24128            let requests_made = Arc::clone(&closure_requests_made);
24129            async move {
24130                assert_eq!(
24131                    params.text_document.uri,
24132                    lsp::Url::from_file_path(path!("/a/first.rs")).unwrap()
24133                );
24134                requests_made.fetch_add(1, atomic::Ordering::Release);
24135                Ok(vec![
24136                    lsp::ColorInformation {
24137                        range: lsp::Range {
24138                            start: lsp::Position {
24139                                line: 0,
24140                                character: 0,
24141                            },
24142                            end: lsp::Position {
24143                                line: 0,
24144                                character: 1,
24145                            },
24146                        },
24147                        color: lsp::Color {
24148                            red: 0.33,
24149                            green: 0.33,
24150                            blue: 0.33,
24151                            alpha: 0.33,
24152                        },
24153                    },
24154                    lsp::ColorInformation {
24155                        range: lsp::Range {
24156                            start: lsp::Position {
24157                                line: 0,
24158                                character: 0,
24159                            },
24160                            end: lsp::Position {
24161                                line: 0,
24162                                character: 1,
24163                            },
24164                        },
24165                        color: lsp::Color {
24166                            red: 0.33,
24167                            green: 0.33,
24168                            blue: 0.33,
24169                            alpha: 0.33,
24170                        },
24171                    },
24172                ])
24173            }
24174        });
24175
24176    let _handle = fake_language_server_without_capabilities
24177        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24178            panic!("Should not be called");
24179        });
24180    cx.executor().advance_clock(Duration::from_millis(100));
24181    color_request_handle.next().await.unwrap();
24182    cx.run_until_parked();
24183    assert_eq!(
24184        1,
24185        requests_made.load(atomic::Ordering::Acquire),
24186        "Should query for colors once per editor open"
24187    );
24188    editor.update_in(cx, |editor, _, cx| {
24189        assert_eq!(
24190            vec![expected_color],
24191            extract_color_inlays(editor, cx),
24192            "Should have an initial inlay"
24193        );
24194    });
24195
24196    // opening another file in a split should not influence the LSP query counter
24197    workspace
24198        .update(cx, |workspace, window, cx| {
24199            assert_eq!(
24200                workspace.panes().len(),
24201                1,
24202                "Should have one pane with one editor"
24203            );
24204            workspace.move_item_to_pane_in_direction(
24205                &MoveItemToPaneInDirection {
24206                    direction: SplitDirection::Right,
24207                    focus: false,
24208                    clone: true,
24209                },
24210                window,
24211                cx,
24212            );
24213        })
24214        .unwrap();
24215    cx.run_until_parked();
24216    workspace
24217        .update(cx, |workspace, _, cx| {
24218            let panes = workspace.panes();
24219            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24220            for pane in panes {
24221                let editor = pane
24222                    .read(cx)
24223                    .active_item()
24224                    .and_then(|item| item.downcast::<Editor>())
24225                    .expect("Should have opened an editor in each split");
24226                let editor_file = editor
24227                    .read(cx)
24228                    .buffer()
24229                    .read(cx)
24230                    .as_singleton()
24231                    .expect("test deals with singleton buffers")
24232                    .read(cx)
24233                    .file()
24234                    .expect("test buffese should have a file")
24235                    .path();
24236                assert_eq!(
24237                    editor_file.as_ref(),
24238                    Path::new("first.rs"),
24239                    "Both editors should be opened for the same file"
24240                )
24241            }
24242        })
24243        .unwrap();
24244
24245    cx.executor().advance_clock(Duration::from_millis(500));
24246    let save = editor.update_in(cx, |editor, window, cx| {
24247        editor.move_to_end(&MoveToEnd, window, cx);
24248        editor.handle_input("dirty", window, cx);
24249        editor.save(
24250            SaveOptions {
24251                format: true,
24252                autosave: true,
24253            },
24254            project.clone(),
24255            window,
24256            cx,
24257        )
24258    });
24259    save.await.unwrap();
24260
24261    color_request_handle.next().await.unwrap();
24262    cx.run_until_parked();
24263    assert_eq!(
24264        3,
24265        requests_made.load(atomic::Ordering::Acquire),
24266        "Should query for colors once per save and once per formatting after save"
24267    );
24268
24269    drop(editor);
24270    let close = workspace
24271        .update(cx, |workspace, window, cx| {
24272            workspace.active_pane().update(cx, |pane, cx| {
24273                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24274            })
24275        })
24276        .unwrap();
24277    close.await.unwrap();
24278    let close = workspace
24279        .update(cx, |workspace, window, cx| {
24280            workspace.active_pane().update(cx, |pane, cx| {
24281                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24282            })
24283        })
24284        .unwrap();
24285    close.await.unwrap();
24286    assert_eq!(
24287        3,
24288        requests_made.load(atomic::Ordering::Acquire),
24289        "After saving and closing all editors, no extra requests should be made"
24290    );
24291    workspace
24292        .update(cx, |workspace, _, cx| {
24293            assert!(
24294                workspace.active_item(cx).is_none(),
24295                "Should close all editors"
24296            )
24297        })
24298        .unwrap();
24299
24300    workspace
24301        .update(cx, |workspace, window, cx| {
24302            workspace.active_pane().update(cx, |pane, cx| {
24303                pane.navigate_backward(window, cx);
24304            })
24305        })
24306        .unwrap();
24307    cx.executor().advance_clock(Duration::from_millis(100));
24308    cx.run_until_parked();
24309    let editor = workspace
24310        .update(cx, |workspace, _, cx| {
24311            workspace
24312                .active_item(cx)
24313                .expect("Should have reopened the editor again after navigating back")
24314                .downcast::<Editor>()
24315                .expect("Should be an editor")
24316        })
24317        .unwrap();
24318    color_request_handle.next().await.unwrap();
24319    assert_eq!(
24320        3,
24321        requests_made.load(atomic::Ordering::Acquire),
24322        "Cache should be reused on buffer close and reopen"
24323    );
24324    editor.update(cx, |editor, cx| {
24325        assert_eq!(
24326            vec![expected_color],
24327            extract_color_inlays(editor, cx),
24328            "Should have an initial inlay"
24329        );
24330    });
24331}
24332
24333#[gpui::test]
24334async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24335    init_test(cx, |_| {});
24336    let (editor, cx) = cx.add_window_view(Editor::single_line);
24337    editor.update_in(cx, |editor, window, cx| {
24338        editor.set_text("oops\n\nwow\n", window, cx)
24339    });
24340    cx.run_until_parked();
24341    editor.update(cx, |editor, cx| {
24342        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24343    });
24344    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24345    cx.run_until_parked();
24346    editor.update(cx, |editor, cx| {
24347        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24348    });
24349}
24350
24351#[track_caller]
24352fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24353    editor
24354        .all_inlays(cx)
24355        .into_iter()
24356        .filter_map(|inlay| inlay.get_color())
24357        .map(Rgba::from)
24358        .collect()
24359}